suppressPackageStartupMessages({
  library(tidyverse)
  library(MOFA2)
  library(Matrix)
  library(SingleCellExperiment)
  library(scran)
  library(glue)
  library(scater)
  library(patchwork)
  library(batchelor)
  library(rhdf5)
  library(ggraph)
  }
  )

Define plotting utils

remove_x_axis <- function(){
  theme(axis.text.x = element_blank(), axis.ticks.x = element_blank(), axis.title.x = element_blank())  
}

remove_y_axis <- function(){
  theme(axis.text.y = element_blank(), axis.ticks.y = element_blank(), axis.title.y = element_blank())  
}

org_colors <- read_csv("~/Pan_fetal_immune/metadata/organ_colors.csv")
Missing column names filled in: 'X1' [1]Error in (function (srcref)  : unimplemented type (29) in 'eval'
figdir <- "~/mount/gdrive/Pan_fetal/Updates_and_presentations/figures/MOFA_analysis/"
if (!dir.exists(figdir)){ dir.create(figdir) }

Load pseudobulked data

indir <- glue("/nfs/team205/ed6/data/Fetal_immune/LMM_data/LMM_input_{split}_PBULK/")
Error: glue cannot interpolate functions into strings.
* object 'split' is a function.
## Plot number of cells per organ/celltype pair
n_cells_heatmap <- data.frame(colData(sce)) %>%
  group_by(anno_lvl_2_final_clean, organ) %>%
  summarise(n_cells=sum(n_cells)) %>%
  ggplot(aes(anno_lvl_2_final_clean, organ)) +
  geom_tile(aes(fill=log10(n_cells))) +
  geom_text(aes(label=n_cells), color="white") +
  scale_fill_viridis_c() +
  theme_classic(base_size = 16) +
  xlab("celltype") +
  theme(axis.text.x = element_blank(), axis.ticks.x = element_blank(), axis.title.x = element_blank())
`summarise()` has grouped output by 'anno_lvl_2_final_clean'. You can override using the `.groups` argument.
n_samples_heatmap <- data.frame(colData(sce)) %>%
  group_by(anno_lvl_2_final_clean, organ) %>%
  summarise(n_samples=n()) %>%
  ggplot(aes(anno_lvl_2_final_clean, organ)) +
  geom_tile(aes(fill=n_samples)) +
  geom_text(aes(label=n_samples), color="white") +
  scale_fill_viridis_c(option="cividis") +
  theme_classic(base_size = 16) +
  xlab("celltype") +
  theme(axis.text.x = element_text(angle=90, hjust=1, vjust=0.5))
`summarise()` has grouped output by 'anno_lvl_2_final_clean'. You can override using the `.groups` argument.
n_cells_heatmap / n_samples_heatmap

Preprocessing

Filtering samples

# Exclude celltypes present in just one organ
keep_ct <- data.frame(colData(sce)) %>%
  select(organ, anno_lvl_2_final_clean) %>%
  distinct() %>%
  group_by(anno_lvl_2_final_clean) %>%
  summarise(n=n()) %>%
  ungroup() %>%
  filter(n > 1) %>%
  pull(anno_lvl_2_final_clean)
Error in (function (classes, fdef, mtable)  : 
  unable to find an inherited method for function 'select' for signature '"data.frame"'
## Plot number of cells per organ/celltype pair
n_cells_heatmap <- data.frame(colData(sce)) %>%
  group_by(anno_lvl_2_final_clean, organ) %>%
  summarise(n_cells=sum(n_cells)) %>%
  ggplot(aes(anno_lvl_2_final_clean, organ)) +
  geom_tile(aes(fill=log10(n_cells))) +
  geom_text(aes(label=n_cells), color="white") +
  scale_fill_viridis_c() +
  theme_classic(base_size = 16) +
  xlab("celltype") +
  theme(axis.text.x = element_blank(), axis.ticks.x = element_blank(), axis.title.x = element_blank())
`summarise()` has grouped output by 'anno_lvl_2_final_clean'. You can override using the `.groups` argument.
n_samples_heatmap <- data.frame(colData(sce)) %>%
  group_by(anno_lvl_2_final_clean, organ) %>%
  summarise(n_samples=n()) %>%
  ggplot(aes(anno_lvl_2_final_clean, organ)) +
  geom_tile(aes(fill=n_samples)) +
  geom_text(aes(label=n_samples), color="white") +
  scale_fill_viridis_c(option="cividis") +
  theme_classic(base_size = 16) +
  xlab("celltype") +
  theme(axis.text.x = element_text(angle=90, hjust=1, vjust=0.5))
`summarise()` has grouped output by 'anno_lvl_2_final_clean'. You can override using the `.groups` argument.
n_cells_heatmap / n_samples_heatmap

Technical effect correction

## Feature selection w scran WITHIN CELLTYPE
anno_groups <- split(colnames(sce), sce$anno_lvl_2_final_clean)
all_hvgs <- c()
for (i in anno_groups){
  dec <- modelGeneVar(sce[,i])
  hvgs <- getTopHVGs(dec, n = 1000)
  all_hvgs <- union(all_hvgs, hvgs)
  }

sce <- sce[which(rowSums(logcounts(sce)) > 0),]
sce
class: SingleCellExperiment 
dim: 28260 993 
metadata(0):
assays(1): logcounts
rownames(28260): TSPAN6 TNMD ... AP000646.1 AP006216.3
rowData names(0):
colnames(993): F45_SK_CD45P_FCAImmP7579224-F45-SK-CD4+T-12-5GEX F45_SK_CD45P_FCAImmP7579224-F45-SK-CD8+T-12-5GEX ...
  F50_SP_CD45P_FCAImmP7803020-F50-SP-IMMATURE_B-15-5GEX F30_TH_CD45N_FCAImmP7277565-F30-TH-ABT(ENTRY)-14-3GEX
colData names(7): Sample donor ... method n_cells
reducedDimNames(0):
altExpNames(0):

EDA with PCA

sce <- runPCA(sce, scale=TRUE, ncomponents=30, 
              exprs_values = "logcounts", subset_row=all_hvgs)

# ## Variance explained
# percent.var <- attr(reducedDim(sce), "percentVar")
# plot(percent.var, log="y", xlab="PC", ylab="Variance explained (%)")
plotPCA(sce, colour_by="donor", ncomponents=6)

plotPCA(sce, colour_by="method", ncomponents=6)

plotPCA(sce, colour_by="organ", ncomponents=10)

Minimize obvious technical effects (3GEX/5GEX) using linear regression (following procedure from OSCA)

## Regress technical effects
design <- model.matrix(~donor+method,data=colData(sce))
residuals <- regressBatches(sce, assay.type = "logcounts", design = design)
assay(sce, "corrected_logcounts") <- as.matrix(assay(residuals[,colnames(sce)], "corrected"))

## Regress organ (soup effect)
design <- model.matrix(~organ,data=colData(sce)) ## Include organ term to capture soup
residuals <- regressBatches(sce, assay.type = "corrected_logcounts", design = design)
assay(sce, "corrected_logcounts") <- as.matrix(assay(residuals[,colnames(sce)], "corrected"))

Check regression has an effect repeating PCA

sce <- runPCA(sce, scale=TRUE, ncomponents=30, exprs_values = "corrected_logcounts")

plotPCA(sce, colour_by="method", ncomponents=6)

plotPCA(sce, colour_by="donor", ncomponents=6)

plotPCA(sce, colour_by="organ", ncomponents=8)

Feature selection

## Feature selection w scran WITHIN CELLTYPE
anno_groups <- split(colnames(sce), sce$anno_lvl_2_final_clean)
all_hvgs <- c()
for (i in anno_groups){
  dec <- modelGeneVar(sce[,i], assay.type = "corrected_logcounts")
  hvgs <- getTopHVGs(dec, n = 1000)
  all_hvgs <- union(all_hvgs, hvgs)
  }

FA Model - Normal MOFA / only celltypes as groups

Make MOFA object (Use celltypes as grouping covariate)

mofa_obj <- readRDS(glue('{indir}LYMPHOID_mofa_obj_organCorrected.RDS'))
Loading required package: MOFA2

Attaching package: ‘MOFA2’

The following object is masked from ‘package:stats’:

    predict
object <- mofa_obj

Prepare 4 training

object
Untrained MOFA model with the following characteristics: 
 Number of views: 1 
 Views names: corrected_logcounts 
 Number of features (per view): 7300 
 Number of groups: 23 
 Groups names: ABT(ENTRY) B1 CD4+T CD8+T CD8AA CYCLING_MPP CYCLING_NK CYCLING_T HSC_MPP ILC3 IMMATURE_B LARGE_PRE_B LATE_PRO_B LMPP_ELP MATURE_B MEMP NK NK_T PRE_PRO_B PRO_B SMALL_PRE_B TH17 TREG 
 Number of samples (per group): 26 32 63 55 24 28 61 38 32 62 29 54 32 10 55 26 87 52 40 50 46 43 48 

Train

Wrapped in run_mofa.R

# install.packages(renv)
# renv::init()
renv::install("reticulate")
renv::use_python()

py_pkgs <- c(
    "scanpy",
    "anndata",
    "mofapy2"
)

reticulate::py_install(py_pkgs)
mofa_trained <- run_mofa(object, outfile = outfile)
Warning: Output file /nfs/team205/ed6/data/Fetal_immune/LMM_data/LMM_input_LYMPHOID_PBULK/LYMPHOID_mofa_model_oneview_organCorrected.hdf5 already exists, it will be replaced
Connecting to the mofapy2 python package using reticulate (use_basilisk = FALSE)... 
    Please make sure to manually specify the right python binary when loading R with reticulate::use_python(..., force=TRUE) or the right conda environment with reticulate::use_condaenv(..., force=TRUE)
    If you prefer to let us automatically install a conda environment with 'mofapy2' installed using the 'basilisk' package, please use the argument 'use_basilisk = TRUE'

        #########################################################
        ###           __  __  ____  ______                    ### 
        ###          |  \/  |/ __ \|  ____/\    _             ### 
        ###          | \  / | |  | | |__ /  \ _| |_           ### 
        ###          | |\/| | |  | |  __/ /\ \_   _|          ###
        ###          | |  | | |__| | | / ____ \|_|            ###
        ###          |_|  |_|\____/|_|/_/    \_\              ###
        ###                                                   ### 
        ######################################################### 
       
 
        
Successfully loaded view='corrected_logcounts' group='ABT(ENTRY)' with N=26 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='B1' with N=32 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='CD4+T' with N=63 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='CD8+T' with N=55 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='CD8AA' with N=24 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='CYCLING_MPP' with N=28 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='CYCLING_NK' with N=61 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='CYCLING_T' with N=38 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='HSC_MPP' with N=32 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='ILC3' with N=62 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='IMMATURE_B' with N=29 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='LARGE_PRE_B' with N=54 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='LATE_PRO_B' with N=32 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='LMPP_ELP' with N=10 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='MATURE_B' with N=55 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='MEMP' with N=26 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='NK' with N=87 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='NK_T' with N=52 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='PRE_PRO_B' with N=40 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='PRO_B' with N=50 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='SMALL_PRE_B' with N=46 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='TH17' with N=43 samples and D=7300 features...
Successfully loaded view='corrected_logcounts' group='TREG' with N=48 samples and D=7300 features...


WARNING: 'ard_factors' in model_options should be set to True if using multiple groups unless you are using MEFISTO

Model options:
- Automatic Relevance Determination prior on the factors: False
- Automatic Relevance Determination prior on the weights: True
- Spike-and-slab prior on the factors: False
- Spike-and-slab prior on the weights: True
Likelihoods:
- View 0 (corrected_logcounts): gaussian



Warning: some group(s) have less than 15 samples, MOFA won't be able to learn meaningful factors for these group(s)...



######################################
## Training the model with seed 2020 ##
######################################


ELBO before training: -128754892.30 

Iteration 1: time=12.72, ELBO=1546417.23, deltaELBO=130301309.528 (101.20105512%), Factors=30
Iteration 2: time=12.40, ELBO=2808114.37, deltaELBO=1261697.141 (0.97992171%), Factors=30
Iteration 3: time=12.55, ELBO=3165428.61, deltaELBO=357314.241 (0.27751508%), Factors=30
Iteration 4: time=12.64, ELBO=3248002.54, deltaELBO=82573.933 (0.06413266%), Factors=30
Iteration 5: time=12.77, ELBO=3275153.02, deltaELBO=27150.480 (0.02108695%), Factors=30
Iteration 6: time=12.74, ELBO=3292546.06, deltaELBO=17393.044 (0.01350865%), Factors=30
Iteration 7: time=12.60, ELBO=3304528.49, deltaELBO=11982.426 (0.00930639%), Factors=30
Iteration 8: time=12.40, ELBO=3314496.32, deltaELBO=9967.833 (0.00774171%), Factors=30
Iteration 9: time=12.42, ELBO=3323682.59, deltaELBO=9186.263 (0.00713469%), Factors=30
Iteration 10: time=12.42, ELBO=3332226.53, deltaELBO=8543.946 (0.00663582%), Factors=30
Iteration 11: time=12.37, ELBO=3339946.37, deltaELBO=7719.842 (0.00599577%), Factors=30
Iteration 12: time=12.34, ELBO=3346980.33, deltaELBO=7033.956 (0.00546306%), Factors=30
Iteration 13: time=12.38, ELBO=3353713.28, deltaELBO=6732.948 (0.00522928%), Factors=30
Iteration 14: time=12.39, ELBO=3360238.09, deltaELBO=6524.816 (0.00506763%), Factors=30
Iteration 15: time=12.44, ELBO=3366188.23, deltaELBO=5950.139 (0.00462129%), Factors=30
Iteration 16: time=12.51, ELBO=3371048.61, deltaELBO=4860.380 (0.00377491%), Factors=30
Iteration 17: time=12.89, ELBO=3374570.98, deltaELBO=3522.370 (0.00273572%), Factors=30
Iteration 18: time=12.55, ELBO=3376839.12, deltaELBO=2268.136 (0.00176159%), Factors=30
Iteration 19: time=12.73, ELBO=3378192.86, deltaELBO=1353.743 (0.00105141%), Factors=30
Iteration 20: time=12.52, ELBO=3378991.95, deltaELBO=799.091 (0.00062063%), Factors=30
Iteration 21: time=13.01, ELBO=3379480.81, deltaELBO=488.855 (0.00037968%), Factors=30
Iteration 22: time=13.31, ELBO=3379799.31, deltaELBO=318.506 (0.00024737%), Factors=30
Iteration 23: time=12.88, ELBO=3380022.73, deltaELBO=223.413 (0.00017352%), Factors=30
Iteration 24: time=12.63, ELBO=3380190.96, deltaELBO=168.236 (0.00013066%), Factors=30
Iteration 25: time=12.34, ELBO=3380325.28, deltaELBO=134.315 (0.00010432%), Factors=30
Iteration 26: time=12.58, ELBO=3380438.01, deltaELBO=112.735 (0.00008756%), Factors=30
Iteration 27: time=12.79, ELBO=3380536.80, deltaELBO=98.786 (0.00007672%), Factors=30
Iteration 28: time=12.90, ELBO=3380626.07, deltaELBO=89.271 (0.00006933%), Factors=30
Iteration 29: time=12.76, ELBO=3380708.35, deltaELBO=82.280 (0.00006390%), Factors=30
Iteration 30: time=11.58, ELBO=3380785.55, deltaELBO=77.198 (0.00005996%), Factors=30
Iteration 31: time=11.44, ELBO=3380859.00, deltaELBO=73.448 (0.00005704%), Factors=30
Iteration 32: time=11.31, ELBO=3380929.41, deltaELBO=70.411 (0.00005469%), Factors=30
Iteration 33: time=11.48, ELBO=3380997.29, deltaELBO=67.881 (0.00005272%), Factors=30
Iteration 34: time=11.42, ELBO=3381063.10, deltaELBO=65.813 (0.00005112%), Factors=30
Iteration 35: time=11.60, ELBO=3381127.15, deltaELBO=64.045 (0.00004974%), Factors=30
Iteration 36: time=12.96, ELBO=3381189.61, deltaELBO=62.460 (0.00004851%), Factors=30

Converged!



#######################
## Training finished ##
#######################


Warning: Output file /nfs/team205/ed6/data/Fetal_immune/LMM_data/LMM_input_LYMPHOID_PBULK/LYMPHOID_mofa_model_oneview_organCorrected.hdf5 already exists, it will be replaced
Saving model in /nfs/team205/ed6/data/Fetal_immune/LMM_data/LMM_input_LYMPHOID_PBULK/LYMPHOID_mofa_model_oneview_organCorrected.hdf5...
23 factors were found to explain little or no variance and they were removed for downstream analysis. You can disable this option by setting load_model(..., remove_inactive_factors = F)
Error in .quality_control(object, verbose = verbose) : 
  !duplicated(unlist(samples_names(object))) are not all TRUE
  # Remove inactive factors
  if (remove_inactive_factors) {
    r2 <- rowSums(do.call('cbind', lapply(object@cache[["variance_explained"]]$r2_per_factor, rowSums, na.rm=TRUE)))
    var.threshold <- 0.0001
    if (all(r2 < var.threshold)) {
      warning(sprintf("All %s factors were found to explain little or no variance so remove_inactive_factors option has been disabled.", length(r2)))
    } else if (any(r2 < var.threshold)) {
      object <- subset_factors(object, which(r2>=var.threshold), recalculate_variance_explained=FALSE)
      message(sprintf("%s factors were found to explain no variance and they were removed for downstream analysis. You can disable this option by setting load_model(..., remove_inactive_factors = FALSE)", sum(r2 < var.threshold)))
    }
  }
Error in subset_factors(object, which(r2 >= var.threshold), recalculate_variance_explained = FALSE) : 
  unused argument (recalculate_variance_explained = FALSE)

Visualize variance explained by factors

plot_variance_explained(mofa_trained, x='factor', y='group', split_by = 'view', plot_total = TRUE, max_r2 = 50)[[1]] +
  theme(axis.text.x = element_text(angle=45, hjust=1))

get_variance_explained(mofa_trained, as.data.frame = TRUE)[[2]] %>%
  ggplot(aes(group, value)) +
  geom_col() +
  coord_flip() +
  ylab("Var. (%)") +
  theme_classic(base_size=14)

Plot by celltype

get_variance_explained(mofa_trained, as.data.frame = TRUE)[[1]] %>%
  ggplot(aes(factor, value)) + geom_col() +
  coord_flip() +
  facet_wrap(group~., ncol = 6, scales = "free_x")
plot_factor_cor(mofa_trained, method = "spearman")
## Correlation with principal components
pcs <- reducedDim(sce)
fctrs <- get_factors(mofa_trained) %>%
  purrr::reduce(rbind)

corrplot::corrplot(cor(pcs, fctrs[rownames(pcs),]))

Factor ID plots

plot_factor_ordered <- function(mofa_trained, f){
  factor_df <- get_factors(mofa_trained, factors = f, as.data.frame = TRUE) %>%
      mutate(organ = sapply(str_split(sample, "_"), function(x) x[2])) %>%
      group_by(group) %>%
      mutate(gr_mean = median(value)) %>%
      ungroup() %>%
      arrange(gr_mean) %>%
      mutate(group=factor(group, levels=unique(group))) 
  
  r2_df <- get_variance_explained(mofa_trained, factors = f, as.data.frame = TRUE)[[1]] %>%
    filter(factor==paste0('Factor',f)) %>%
    mutate(group=factor(group, levels = levels(factor_df$group)))
  
  pl1 <- factor_df %>%
      ggplot(aes(group, value)) +
      geom_boxplot() +
      geom_jitter(aes(color= organ), size=0.7) +
      geom_hline(yintercept = 0, linetype=2) +
      coord_flip() +
      ylab(paste0("Factor ", f)) +
      theme_bw(base_size = 14)
  
  pl2 <- r2_df %>%
    ggplot(aes(group, value)) +
    geom_col() +
    coord_flip() +
    ylab("% variance explained") +
    theme_bw(base_size = 14) +
    remove_y_axis()
  
  pl1 + pl2 + plot_layout(widths=c(2,1), guides="collect") 
}

get_top_celltype_per_factor <- function(mofa_trained, f){
  r2_df <- get_variance_explained(mofa_trained, factors = f, as.data.frame = TRUE)[[1]] %>%
    filter(factor==paste0('Factor',f)) 
    # mutate(group=factor(group, levels = ))
  top_quant_r2 <- quantile(r2_df$value, probs = seq(0, 1, by = 0.2))["80%"]
  top_groups <- r2_df$group[r2_df$value >= top_quant_r2]
  return(top_groups)
}

save_factor_id <- function(mofa_trained, f, figdir){
  ## Order celltypes by factor values
  p1 <- plot_factor_ordered(mofa_trained, f)
  
  ## Plot factor values across organs for celltypes with high variance explained
  p2 <- plot_factor(mofa_trained, factors = f, groups = get_top_celltype_per_factor(mofa_trained, f), group_by = "group", 
              color_by = "organ", 
              dot_size = 2, dodge = TRUE
              )
  
  ## Plot factor weights on genes
  # plot_data_heatmap(mofa_trained, factor = f, nfeatures = 50, text_size = 3, show_colnames=FALSE,
  #                   annotation_samples = c("organ", "time", "method", "donor"))
  p3 <- plot_weights(mofa_trained, factors = f, nfeatures = 30, text_size = 3) +
   scale_y_discrete(expand=c(0.1, 0.1))
  
  (p1 | (p2 / p3)) +
    plot_layout(guides="collect") +
    ggsave(glue("{figdir}/MOFA_{split}_factorID_factor{f}.pdf"), width = 15, height = 10)
}

for (f in 1:mofa_trained@dimensions$K){
  print(paste0("Saving ID for Factor ", f, "..."))
  save_factor_id(mofa_trained, f=f, figdir = figdir)  
}

# save_factor_id(mofa_trained, f=1, figdir = figdir)  
# plot_weights(mofa_trained, factors = f, nfeatures = 30, text_size = 3) +
#    scale_y_discrete(expand=c(0.1, 0.1))

KNN graph per celltype

## Get factors that explain most variance in each celltype
get_top_factor_per_celltype <- function(mofa_trained, gr, min_R2=2){
  get_variance_explained(mofa_trained, as.data.frame = TRUE)[[1]] %>%
    filter(group==gr) %>%
    filter(value >= min_R2) %>%
    pull(factor) %>%
    as.character()
}

## Make KNN graph based on similarity of top factors for each celltype
get_ct_KNN_graph <- function(mofa_trained, gr, min_R2=5, k=5){
  ## Get factors that explain most variance per celltype
  fs <- get_top_factor_per_celltype(mofa_trained, gr, min_R2 = min_R2)
  
  ## Make KNN graph from top factors
  Z <- get_factors(mofa_trained, groups=gr, factors = fs)[[1]]
  knn_ct <- buildKNNGraph(t(Z), k=k)
  
  ## Add attributes
  metadata_ct <- samples_metadata(mofa_trained)[rownames(Z),]
  # covariates
  V(knn_ct)$organ <- metadata_ct$organ
  V(knn_ct)$age <- metadata_ct$age
  V(knn_ct)$n_cells <- metadata_ct$n_cells
  V(knn_ct)$method <- metadata_ct$method
  V(knn_ct)$donor <- metadata_ct$donor
  # top factors
  for (c in colnames(Z)){
   vertex_attr(knn_ct)[[c]] <- Z[,c]  
  }
  
  return(knn_ct)
  }

## Plot KNN graph
plot_ct_KNN_graph <- function(knn, color_by="organ"){
  ## Define color 
  if (!color_by %in% names(vertex_attr(knn))){
    stop("specified color_by variable is not in vertex_attr(knn)")
  }
  
  if (color_by=="organ"){ 
    scale_color_knngraph <- scale_color_manual(values=org_colors)
  } else if (is.numeric(vertex_attr(knn, color_by))){
    scale_color_knngraph <- scale_color_viridis_c(option="magma")  
  } else {
      scale_color_knngraph <- scale_color_discrete()
    }
  
  vertex_attr(knn, "color_by") <- vertex_attr(knn, color_by)
  
  ggraph(knn) +
    geom_edge_link0() +
    geom_node_point(aes(color=color_by, size=n_cells)) +
    theme(panel.background = element_blank()) +
    scale_color_knngraph +
    scale_size(range=c(2,7)) 
  }

get_top_factor_per_celltype(mofa_trained, "CD8+T", min_R2 = 5)
plot_ct_KNN_graph(get_ct_KNN_graph(mofa_trained, "SMALL PRE B CELL", k=5), color_by = 'organ') +
  plot_ct_KNN_graph(get_ct_KNN_graph(mofa_trained, "SMALL PRE B CELL", k=5), color_by = 'Factor4')

all_groups <- names(get_data(mofa_trained)[[1]])
knn_graph_pl <- lapply(all_groups, function(g){
  knn <- get_ct_KNN_graph(mofa_trained, g, k=5, min_R2 = 2)
  plot_ct_KNN_graph(knn, color_by = 'organ') + ggtitle(g)
  })

knn_graph_pl <- setNames(knn_graph_pl, all_groups)
knn_graph_pl$TREG
## Score connectivity between samples from the same organ
.calc_connectivity_score <- function(knn, o){
  adj <- get.adjacency(knn)
  n_org <- sum(V(knn)$organ==o)
  n_other <- sum(V(knn)$organ!=o)
  within_edges <- sum(adj[V(knn)$organ==o,V(knn)$organ==o])
  between_edges <- sum(adj[V(knn)$organ==o,V(knn)$organ!=o])
  score <- (within_edges/between_edges)*(n_other/n_org)
  return(score)
  }

## Calculate connectivity score for permutations of node labels
conn_score_test <- function(knn, o, n_perm=1000){
  real_score <- .calc_connectivity_score(knn, o)
  ## Random permutations
  rand_scores <- c()
  for (i in 1:n_perm){
    rand_knn <- knn
    V(rand_knn)$organ <- sample(V(knn)$organ)
    rand_scores <- c(rand_scores, .calc_connectivity_score(rand_knn, o))   
  }
  
  p_val <- sum(c(rand_scores, real_score) >= real_score)/(n_perm + 1)
  if (p_val < 2e-16){ p_val <- 2e-16}
  return(c('score'=real_score,'p_value'=p_val))
}

## Calculate connectivity score + significance with permutation test
test_conn_group <- function(mofa_trained, g, k=5, min_R2 = 2, n_perm=1000){
  knn <- get_ct_KNN_graph(mofa_trained, g, k=k, min_R2 = min_R2)
  test_orgs <- names(table(V(knn)$organ))[table(V(knn)$organ) > 2]
  return(sapply(test_orgs, function(o) conn_score_test(knn, o, n_perm=n_perm)))
  }

connectivity_test_ls <- lapply(all_groups, function(g) test_conn_group(mofa_trained, g))
connectivity_test_ls <- setNames(connectivity_test_ls, all_groups)

connectivity_test_df <- imap(connectivity_test_ls, ~ data.frame(t(.x)) %>% rownames_to_column("organ") %>% mutate(group=.y)) %>%
  purrr::reduce(bind_rows) %>%
  mutate(is_signif = ifelse(p_value < 0.01, TRUE, FALSE)) 

connectivity_test_df %>%
  ggplot(aes(organ, group,fill=log10(score))) +
  geom_tile() +
  scale_fill_distiller(palette="Reds", direction = 1) +
  geom_text(data=. %>% filter(is_signif), label="*", size=5)
connectivity_test_df %>%
  group_by(group) %>%
  mutate(mean_val=median(score)) %>%
  ungroup() %>%
  arrange(-mean_val) %>%
  mutate(group=factor(group, levels=unique(group))) %>%
  ggplot(aes(organ, log1p(score))) +
  geom_col(fill="grey") +
  geom_col(data=. %>% filter(is_signif), aes(fill=organ)) +
  scale_fill_manual(values=org_colors)  +
  coord_flip() +
  facet_grid(group~.) +
  theme(strip.text.y = element_text(angle=0))

Expression of top R2 factors

get_top_weight_genes <- function(mofa_trained, f, n_top=20, which="top"){
  w_df <- get_weights(mofa_trained, factors = f, as.data.frame = TRUE) %>%
    arrange(value) 
  if (which=="top") {
    w_df %>%
      top_n(n_top, value) %>%
      pull(feature) %>%
      as.character()
  } else if (which=="bottom"){
    w_df %>%
      top_n(n_top, -value) %>%
      pull(feature) %>%
      as.character()
    }
}

plot_data_top_weights <- function(mofa_trained, ct, f, n_top=20, which="top"){
  genes <- get_top_weight_genes(mofa_trained, f, which=which, n_top=n_top)
  data <- get_data(mofa_trained, groups=ct)[[1]][[1]][genes,]
  
  pl_df <- reshape2::melt(data, varnames=c("gene", "sample")) %>%
    left_join(samples_metadata(mofa_trained)) %>%
    arrange(age) %>%
    mutate(sample=factor(sample, levels=unique(sample))) %>%
    group_by(gene) %>%
    mutate(value=scale(value))
  pl_df %>%
    ggplot(aes(sample, gene, fill=value)) +
    geom_tile() +
    facet_grid(.~organ, space="free", scales="free") +
    scale_fill_gradient2(high="red", low="blue", name="Scaled\nexpression") +
    xlab("----age--->") + ylab(glue("{which} weight genes")) +
    theme_bw(base_size=16) +
    theme(axis.ticks.x = element_blank(), axis.text.x = element_blank()) +
    ggtitle(glue('{ct} - {f}'))
}

for (g in all_groups){
  fs <- get_top_factor_per_celltype(mofa_trained, g, min_R2=3)
  top_plots <- lapply(fs, function(x) (plot_data_top_weights(mofa_trained, g, x, which="top") + remove_x_axis()) /  
                        plot_data_top_weights(mofa_trained, g, x, which="bottom") + ggtitle("")
  )
  wrap_plots(top_plots, ncol=1) +
  ggsave(glue("{figdir}/top_factors_expr_{g}.pdf"), width=8, height = 7*length(top_plots))
}
plot_data_heatmap(mofa_trained, factor = 2, show_colnames=FALSE, annotation_samples = c("anno_lvl_2_final_clean", "organ"))
plot_factor(mofa_trained, factor=25, color_by="method", dot_size = 4)

GSEA

# BiocManager::install("MOFAdata")
library(MOFAdata)
utils::data(reactomeGS)
head(rownames(reactomeGS))

## Remove row with NA
reactomeGS <- reactomeGS[!is.na(rownames(reactomeGS)),]
library(EnsDb.Hsapiens.v86)
hg.pairs <- readRDS(system.file("exdata", "human_cycle_markers.rds", package="scran"))
all_genes <- ensembldb::genes(EnsDb.Hsapiens.v86)
detach(package:EnsDb.Hsapiens.v86)
detach(package:ensembldb)

# gene_name_2_id <- function(gene){
#    return(all_genes[all_genes$gene_name==gene,]$gene_id[1])
# }
# 
# gene_ids <- sapply(mofa_trained@features_metadata$feature, gene_name_2_id)
# rowData(sce)["gene_id"] <- gene_ids
# rowData(sce)["gene_name"] <- rownames(sce)

gene_names_reactome <- all_genes[colnames(reactomeGS)]$gene_name
colnames(reactomeGS) <- gene_names_reactome

Subset to genes tested

reactomeGS_universe <- reactomeGS[, colnames(reactomeGS) %in% mofa_trained@features_metadata$feature]
# GSEA on positive weights, with default options
res.positive <- run_enrichment(mofa_trained,
  view='scaled_logcounts',
  # statistical.test = 'cor.adj.parametric',
  feature.sets = reactomeGS_universe, 
  sign = "positive",
)

# GSEA on negative weights, with default options
res.negative <- run_enrichment(mofa_trained, 
  view='scaled_logcounts',
  # statistical.test = 'cor.adj.parametric',
  feature.sets = reactomeGS_universe, 
  sign = "negative"
)


for (f in 1:mofa_trained@dimensions$K){
  if (min(res.positive$pval.adj[,paste0("Factor", f)]) < 0.1) {
    print(plot_enrichment(res.positive, factor = f, alpha=0.1) + ggtitle("Positive weights") +
            plot_enrichment(res.negative, factor = f, alpha=0.1) + ggtitle("Negative weights") +
              plot_annotation(title=paste0("Factor", f)))
      }
  }
signif_pathways <- rownames(data.frame(res.negative$pval.adj))[order(data.frame(res.negative$pval.adj)[["Factor8"]])[0:10]]
colnames(reactomeGS_universe)[reactomeGS_universe[signif_pathways[5],]==1]
plot_enrichment_detailed(res.negative, factor = 8)

–> –> –> –> –> –> –> –> –> –> –> –> –> –> –> –> –>

–> –> –> –> –>

–> –> –>

LS0tCnRpdGxlOiAiRmFjdG9yIEFuYWx5c2lzIGZvciB3aXRoaW4tY2VsbHR5cGUgZGlmZmVyZW5jZXMgb24gb24gcGFuLWZldGFsIGltbXVuZSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQpgYGB7cn0Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKHsKICBsaWJyYXJ5KHRpZHl2ZXJzZSkKICBsaWJyYXJ5KE1PRkEyKQogIGxpYnJhcnkoTWF0cml4KQogIGxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCiAgbGlicmFyeShzY3JhbikKICBsaWJyYXJ5KGdsdWUpCiAgbGlicmFyeShzY2F0ZXIpCiAgbGlicmFyeShwYXRjaHdvcmspCiAgbGlicmFyeShiYXRjaGVsb3IpCiAgbGlicmFyeShyaGRmNSkKICBsaWJyYXJ5KGdncmFwaCkKICB9CiAgKQpgYGAKCkRlZmluZSBwbG90dGluZyB1dGlscwpgYGB7cn0KcmVtb3ZlX3hfYXhpcyA8LSBmdW5jdGlvbigpewogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSkgIAp9CgpyZW1vdmVfeV9heGlzIDwtIGZ1bmN0aW9uKCl7CiAgdGhlbWUoYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpKSAgCn0KCm9yZ19jb2xvcnMgPC0gcmVhZF9jc3YoIn4vUGFuX2ZldGFsX2ltbXVuZS9tZXRhZGF0YS9vcmdhbl9jb2xvcnMuY3N2IikKb3JnX2NvbG9ycyA8LSBzZXROYW1lcyhvcmdfY29sb3JzJGNvbG9yLCBvcmdfY29sb3JzJG9yZ2FuKQpgYGAKCmBgYHtyfQpmaWdkaXIgPC0gIn4vbW91bnQvZ2RyaXZlL1Bhbl9mZXRhbC9VcGRhdGVzX2FuZF9wcmVzZW50YXRpb25zL2ZpZ3VyZXMvTU9GQV9hbmFseXNpcy8iCmlmICghZGlyLmV4aXN0cyhmaWdkaXIpKXsgZGlyLmNyZWF0ZShmaWdkaXIpIH0KYGBgCgojIyBMb2FkIHBzZXVkb2J1bGtlZCBkYXRhCgpgYGB7cn0Kc3BsaXQgPSAiTFlNUEhPSUQiCmluZGlyIDwtIGdsdWUoIi9uZnMvdGVhbTIwNS9lZDYvZGF0YS9GZXRhbF9pbW11bmUvTE1NX2RhdGEvTE1NX2lucHV0X3tzcGxpdH1fUEJVTEsvIikKCm1hdHJpeCA8LSByZWFkTU0oZmlsZSA9IHBhc3RlMChpbmRpciwgIm1hdHJpeC5tdHguZ3oiKSkKY29sZGF0YSA8LSByZWFkLmNzdihmaWxlID0gcGFzdGUwKGluZGlyLCAibWV0YWRhdGEuY3N2Lmd6IikpICAlPiUKICBjb2x1bW5fdG9fcm93bmFtZXMoIlgiKQpyb3dkYXRhIDwtIHJlYWQuY3N2KGZpbGUgPSBwYXN0ZTAoaW5kaXIsICJnZW5lLmNzdi5neiIpKSAKCiMjIE1ha2UgU2luZ2xlQ2VsbEV4cGVyaW1lbnQgb2JqCnNjZSA8LSBTaW5nbGVDZWxsRXhwZXJpbWVudChsaXN0KGxvZ2NvdW50cyA9IHQobWF0cml4KSksIGNvbERhdGEgPSBjb2xkYXRhKQpyb3duYW1lcyhzY2UpIDwtIG1ha2UudW5pcXVlKHJvd2RhdGEkR2VuZU5hbWUpIApgYGAKCmBgYHtyLCBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9MTB9CiMjIFBsb3QgbnVtYmVyIG9mIGNlbGxzIHBlciBvcmdhbi9jZWxsdHlwZSBwYWlyCm5fY2VsbHNfaGVhdG1hcCA8LSBkYXRhLmZyYW1lKGNvbERhdGEoc2NlKSkgJT4lCiAgZ3JvdXBfYnkoYW5ub19sdmxfMl9maW5hbF9jbGVhbiwgb3JnYW4pICU+JQogIHN1bW1hcmlzZShuX2NlbGxzPXN1bShuX2NlbGxzKSkgJT4lCiAgZ2dwbG90KGFlcyhhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuLCBvcmdhbikpICsKICBnZW9tX3RpbGUoYWVzKGZpbGw9bG9nMTAobl9jZWxscykpKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1uX2NlbGxzKSwgY29sb3I9IndoaXRlIikgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkgKwogIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTYpICsKICB4bGFiKCJjZWxsdHlwZSIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpCgpuX3NhbXBsZXNfaGVhdG1hcCA8LSBkYXRhLmZyYW1lKGNvbERhdGEoc2NlKSkgJT4lCiAgZ3JvdXBfYnkoYW5ub19sdmxfMl9maW5hbF9jbGVhbiwgb3JnYW4pICU+JQogIHN1bW1hcmlzZShuX3NhbXBsZXM9bigpKSAlPiUKICBnZ3Bsb3QoYWVzKGFubm9fbHZsXzJfZmluYWxfY2xlYW4sIG9yZ2FuKSkgKwogIGdlb21fdGlsZShhZXMoZmlsbD1uX3NhbXBsZXMpKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1uX3NhbXBsZXMpLCBjb2xvcj0id2hpdGUiKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Mob3B0aW9uPSJjaXZpZGlzIikgKwogIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTYpICsKICB4bGFiKCJjZWxsdHlwZSIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT05MCwgaGp1c3Q9MSwgdmp1c3Q9MC41KSkKCm5fY2VsbHNfaGVhdG1hcCAvIG5fc2FtcGxlc19oZWF0bWFwCmBgYAoKIyMgUHJlcHJvY2Vzc2luZwoKIyMjIEZpbHRlcmluZyBzYW1wbGVzCgpgYGB7cn0KIyMgRmlsdGVyIG91dCBzYW1wbGVzIHdpdGggbGVzcyB0aGFuIDIwIGNlbGxzCnNjZSA8LSBzY2VbLHNjZSRuX2NlbGxzID4gMjBdCgojIEV4Y2x1ZGUgY2VsbHR5cGVzIHByZXNlbnQgaW4ganVzdCBvbmUgb3JnYW4Ka2VlcF9jdCA8LSBkYXRhLmZyYW1lKGNvbERhdGEoc2NlKSkgJT4lCiAgZHBseXI6OnNlbGVjdChvcmdhbiwgYW5ub19sdmxfMl9maW5hbF9jbGVhbikgJT4lCiAgZGlzdGluY3QoKSAlPiUKICBncm91cF9ieShhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuKSAlPiUKICBzdW1tYXJpc2Uobj1uKCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBmaWx0ZXIobiA+IDEpICU+JQogIHB1bGwoYW5ub19sdmxfMl9maW5hbF9jbGVhbikKCnNjZSA8LSBzY2VbLHNjZSRhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuICVpbiUga2VlcF9jdF0KCiMgRmlsdGVyIG91dCBjZWxsdHlwZXMgd2l0aCBsZXNzIHRoYW4gMTAgc2FtcGxlcwprZWVwX2N0IDwtIGRhdGEuZnJhbWUoY29sRGF0YShzY2UpKSAlPiUKICBncm91cF9ieShhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuKSAlPiUKICBzdW1tYXJpc2Uobl9zYW1wbGVzPW4oKSkgJT4lCiAgZmlsdGVyKG5fc2FtcGxlcyA+PSAxMCkgJT4lCiAgcHVsbChhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuKQoKc2NlIDwtIHNjZVssc2NlJGFubm9fbHZsXzJfZmluYWxfY2xlYW4gJWluJSBrZWVwX2N0XQoKIyMgRXhjbHVkZSBsb3cgcXVhbGl0eSBjbHVzdGVycwpsaWJyYXJ5KCJyanNvbiIpCmFubm9fZ3JvdXBzIDwtIGZyb21KU09OKGZpbGUgPSAifi9QYW5fZmV0YWxfaW1tdW5lL21ldGFkYXRhL2Fubm9fZ3JvdXBzLmpzb24iKQpzY2UgPC0gc2NlWywhc2NlJGFubm9fbHZsXzJfZmluYWxfY2xlYW4gJWluJSBhbm5vX2dyb3VwcyRPVEhFUl0KCiMjIEV4Y2x1ZGUgZG9ub3IgRjE5IChsb3cgUSkKc2NlIDwtIHNjZVssIXNjZSRkb25vciAlaW4lIGMoJ0YxOScpXQpgYGAKCmBgYHtyLCBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9MTB9CiMjIFBsb3QgbnVtYmVyIG9mIGNlbGxzIHBlciBvcmdhbi9jZWxsdHlwZSBwYWlyCm5fY2VsbHNfaGVhdG1hcCA8LSBkYXRhLmZyYW1lKGNvbERhdGEoc2NlKSkgJT4lCiAgZ3JvdXBfYnkoYW5ub19sdmxfMl9maW5hbF9jbGVhbiwgb3JnYW4pICU+JQogIHN1bW1hcmlzZShuX2NlbGxzPXN1bShuX2NlbGxzKSkgJT4lCiAgZ2dwbG90KGFlcyhhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuLCBvcmdhbikpICsKICBnZW9tX3RpbGUoYWVzKGZpbGw9bG9nMTAobl9jZWxscykpKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1uX2NlbGxzKSwgY29sb3I9IndoaXRlIikgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkgKwogIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTYpICsKICB4bGFiKCJjZWxsdHlwZSIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpCgpuX3NhbXBsZXNfaGVhdG1hcCA8LSBkYXRhLmZyYW1lKGNvbERhdGEoc2NlKSkgJT4lCiAgZ3JvdXBfYnkoYW5ub19sdmxfMl9maW5hbF9jbGVhbiwgb3JnYW4pICU+JQogIHN1bW1hcmlzZShuX3NhbXBsZXM9bigpKSAlPiUKICBnZ3Bsb3QoYWVzKGFubm9fbHZsXzJfZmluYWxfY2xlYW4sIG9yZ2FuKSkgKwogIGdlb21fdGlsZShhZXMoZmlsbD1uX3NhbXBsZXMpKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1uX3NhbXBsZXMpLCBjb2xvcj0id2hpdGUiKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Mob3B0aW9uPSJjaXZpZGlzIikgKwogIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTYpICsKICB4bGFiKCJjZWxsdHlwZSIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT05MCwgaGp1c3Q9MSwgdmp1c3Q9MC41KSkKCm5fY2VsbHNfaGVhdG1hcCAvIG5fc2FtcGxlc19oZWF0bWFwCmBgYAoKIyMjIFRlY2huaWNhbCBlZmZlY3QgY29ycmVjdGlvbiAKCmBgYHtyfQojIyBGZWF0dXJlIHNlbGVjdGlvbiB3IHNjcmFuIFdJVEhJTiBDRUxMVFlQRQphbm5vX2dyb3VwcyA8LSBzcGxpdChjb2xuYW1lcyhzY2UpLCBzY2UkYW5ub19sdmxfMl9maW5hbF9jbGVhbikKYWxsX2h2Z3MgPC0gYygpCmZvciAoaSBpbiBhbm5vX2dyb3Vwcyl7CiAgZGVjIDwtIG1vZGVsR2VuZVZhcihzY2VbLGldKQogIGh2Z3MgPC0gZ2V0VG9wSFZHcyhkZWMsIG4gPSAxMDAwKQogIGFsbF9odmdzIDwtIHVuaW9uKGFsbF9odmdzLCBodmdzKQogIH0KCnNjZSA8LSBzY2Vbd2hpY2gocm93U3Vtcyhsb2djb3VudHMoc2NlKSkgPiAwKSxdCnNjZQpgYGAKCkVEQSB3aXRoIFBDQQpgYGB7ciwgZmlnLmhlaWdodD0xNSwgZmlnLndpZHRoPTE1fQpzY2UgPC0gcnVuUENBKHNjZSwgc2NhbGU9VFJVRSwgbmNvbXBvbmVudHM9MzAsIAogICAgICAgICAgICAgIGV4cHJzX3ZhbHVlcyA9ICJsb2djb3VudHMiLCBzdWJzZXRfcm93PWFsbF9odmdzKQoKIyAjIyBWYXJpYW5jZSBleHBsYWluZWQKIyBwZXJjZW50LnZhciA8LSBhdHRyKHJlZHVjZWREaW0oc2NlKSwgInBlcmNlbnRWYXIiKQojIHBsb3QocGVyY2VudC52YXIsIGxvZz0ieSIsIHhsYWI9IlBDIiwgeWxhYj0iVmFyaWFuY2UgZXhwbGFpbmVkICglKSIpCnBsb3RQQ0Eoc2NlLCBjb2xvdXJfYnk9ImRvbm9yIiwgbmNvbXBvbmVudHM9NikKcGxvdFBDQShzY2UsIGNvbG91cl9ieT0ibWV0aG9kIiwgbmNvbXBvbmVudHM9NikKcGxvdFBDQShzY2UsIGNvbG91cl9ieT0ib3JnYW4iLCBuY29tcG9uZW50cz0xMCkKYGBgCgpNaW5pbWl6ZSBvYnZpb3VzIHRlY2huaWNhbCBlZmZlY3RzICgzR0VYLzVHRVgpIHVzaW5nIGxpbmVhciByZWdyZXNzaW9uIChmb2xsb3dpbmcgcHJvY2VkdXJlIGZyb20gW09TQ0FdKGh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9ib29rcy9yZWxlYXNlL09TQ0EvaW50ZWdyYXRpbmctZGF0YXNldHMuaHRtbCNsaW5lYXItcmVncmVzc2lvbikpCgpgYGB7cn0KIyMgUmVncmVzcyB0ZWNobmljYWwgZWZmZWN0cwpkZXNpZ24gPC0gbW9kZWwubWF0cml4KH5kb25vcittZXRob2QsZGF0YT1jb2xEYXRhKHNjZSkpCnJlc2lkdWFscyA8LSByZWdyZXNzQmF0Y2hlcyhzY2UsIGFzc2F5LnR5cGUgPSAibG9nY291bnRzIiwgZGVzaWduID0gZGVzaWduKQphc3NheShzY2UsICJjb3JyZWN0ZWRfbG9nY291bnRzIikgPC0gYXMubWF0cml4KGFzc2F5KHJlc2lkdWFsc1ssY29sbmFtZXMoc2NlKV0sICJjb3JyZWN0ZWQiKSkKCiMjIFJlZ3Jlc3Mgb3JnYW4gKHNvdXAgZWZmZWN0KQpkZXNpZ24gPC0gbW9kZWwubWF0cml4KH5vcmdhbixkYXRhPWNvbERhdGEoc2NlKSkgIyMgSW5jbHVkZSBvcmdhbiB0ZXJtIHRvIGNhcHR1cmUgc291cApyZXNpZHVhbHMgPC0gcmVncmVzc0JhdGNoZXMoc2NlLCBhc3NheS50eXBlID0gImNvcnJlY3RlZF9sb2djb3VudHMiLCBkZXNpZ24gPSBkZXNpZ24pCmFzc2F5KHNjZSwgImNvcnJlY3RlZF9sb2djb3VudHMiKSA8LSBhcy5tYXRyaXgoYXNzYXkocmVzaWR1YWxzWyxjb2xuYW1lcyhzY2UpXSwgImNvcnJlY3RlZCIpKQoKYGBgCgpDaGVjayByZWdyZXNzaW9uIGhhcyBhbiBlZmZlY3QgcmVwZWF0aW5nIFBDQQpgYGB7ciwgZmlnLmhlaWdodD0xNSwgZmlnLndpZHRoPTE1fQpzY2UgPC0gcnVuUENBKHNjZSwgc2NhbGU9VFJVRSwgbmNvbXBvbmVudHM9MzAsIGV4cHJzX3ZhbHVlcyA9ICJjb3JyZWN0ZWRfbG9nY291bnRzIikKCnBsb3RQQ0Eoc2NlLCBjb2xvdXJfYnk9Im1ldGhvZCIsIG5jb21wb25lbnRzPTYpCnBsb3RQQ0Eoc2NlLCBjb2xvdXJfYnk9ImRvbm9yIiwgbmNvbXBvbmVudHM9NikKcGxvdFBDQShzY2UsIGNvbG91cl9ieT0ib3JnYW4iLCBuY29tcG9uZW50cz04KQpgYGAKCiMjIyBGZWF0dXJlIHNlbGVjdGlvbgoKYGBge3J9CiMjIEZlYXR1cmUgc2VsZWN0aW9uIHcgc2NyYW4gV0lUSElOIENFTExUWVBFCmFubm9fZ3JvdXBzIDwtIHNwbGl0KGNvbG5hbWVzKHNjZSksIHNjZSRhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuKQphbGxfaHZncyA8LSBjKCkKZm9yIChpIGluIGFubm9fZ3JvdXBzKXsKICBkZWMgPC0gbW9kZWxHZW5lVmFyKHNjZVssaV0sIGFzc2F5LnR5cGUgPSAiY29ycmVjdGVkX2xvZ2NvdW50cyIpCiAgaHZncyA8LSBnZXRUb3BIVkdzKGRlYywgbiA9IDEwMDApCiAgYWxsX2h2Z3MgPC0gdW5pb24oYWxsX2h2Z3MsIGh2Z3MpCiAgfQpgYGAKCjwhLS0gYGBge3J9IC0tPgo8IS0tIGRhdGEuZnJhbWUoY29sRGF0YShzY2UpKSAlPiUgLS0+CjwhLS0gICBncm91cF9ieShhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuLCBvcmdhbikgJT4lIC0tPgo8IS0tICAgc3VtbWFyaXNlKG5fc2FtcGxlcz1uKCksIG5fY2VsbHM9c3VtKG5fY2VsbHMpKSAlPiUgLS0+CjwhLS0gICBnZ3Bsb3QoYWVzKG5fc2FtcGxlcywgbG9nMTAobl9jZWxscykpKSArIC0tPgo8IS0tICAgZ2VvbV9wb2ludChzaXplPTAuOCwgYWxwaGE9MC42KSAtLT4KPCEtLSBgYGAgLS0+Cgo8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTB9IC0tPgo8IS0tIHAgPC0gZGF0YS5mcmFtZShyZWR1Y2VkRGltKHNjZSlbLDE6Ml0pICU+JSAtLT4KPCEtLSAgIG11dGF0ZShvcmdhbj1zY2Ukb3JnYW4sIGNlbGx0eXBlPXNjZSRhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoY29sb3I9aWZlbHNlKGNlbGx0eXBlPT0iTUFUVVJFIEIgQ0VMTCIsICJFTFAiLCBOQSkpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoUEMxLCBQQzIpKSArIC0tPgo8IS0tICAgZ2VvbV9wb2ludChjb2xvcj0iZ3JleSIpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KGRhdGE9LiAlPiUgZmlsdGVyKCFpcy5uYShjb2xvcikpLCBhZXMoY29sb3I9b3JnYW4pLCBzaXplPTIpICsgLS0+CjwhLS0gICBnZW9tX3J1ZyhkYXRhPS4gJT4lIGZpbHRlcighaXMubmEoY29sb3IpKSwgYWVzKGNvbG9yPW9yZ2FuKSwgYWxwaGE9MC41KSAtLT4KCjwhLS0gcCAtLT4KPCEtLSBgYGAgLS0+Cgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBsaWJyYXJ5KFJDb2xvckJyZXdlcikgLS0+CjwhLS0gb3JnX2NvbG9ycyA8LSBzZXROYW1lcyhicmV3ZXIucGFsKDksICJTZXQxIiksIHVuaXF1ZShzY2Ukb3JnYW4pKSAtLT4KPCEtLSBgYGAgLS0+CgoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gc2NlX21hdHVyZUIgPC0gc2NlWyxzY2UkYW5ub19sdmxfMl9maW5hbF9jbGVhbj09Ik1BVFVSRSBCIENFTEwiXSAtLT4KPCEtLSBhc3NheShzY2VfbWF0dXJlQiwgInNjYWxlZF9sb2djb3VudHMiKSA8LSB0KHNjYWxlKHQobG9nY291bnRzKHNjZV9tYXR1cmVCKSkpKSAtLT4KCgo8IS0tIHNjZV9tYXR1cmVCIDwtIHJ1blBDQShzY2VfbWF0dXJlQiwgc2NhbGU9RkFMU0UsIG5jb21wb25lbnRzPTMwLCBleHByc192YWx1ZXMgPSAic2NhbGVkX2xvZ2NvdW50cyIpIC0tPgoKPCEtLSBkYXRhLmZyYW1lKHJlZHVjZWREaW0oc2NlX21hdHVyZUIpWywyOjNdKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUob3JnYW49c2NlX21hdHVyZUIkb3JnYW4sIGNlbGx0eXBlPXNjZV9tYXR1cmVCJGFubm9fbHZsXzJfZmluYWxfY2xlYW4pICU+JSAtLT4KPCEtLSAgIG11dGF0ZShjb2xvcj1pZmVsc2Uob3JnYW4gJWluJSBjKCJUSCIsIkJNIiksICJFTFAiLCBOQSkpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoUEMyLCBQQzMpKSArIC0tPgo8IS0tICAgZ2VvbV9wb2ludChjb2xvcj0iZ3JleSIpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KGRhdGE9LiAlPiUgZmlsdGVyKCFpcy5uYShjb2xvcikpLCBhZXMoY29sb3I9b3JnYW4pLCBzaXplPTIpICsgLS0+CjwhLS0gICBnZW9tX3J1ZyhkYXRhPS4gJT4lIGZpbHRlcighaXMubmEoY29sb3IpKSwgYWVzKGNvbG9yPW9yZ2FuKSwgYWxwaGE9MC41KSArIC0tPgo8IS0tICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1vcmdfY29sb3JzKSArIC0tPgo8IS0tICAgdGhlbWVfYncoYmFzZV9zaXplPTE2KSArIC0tPgo8IS0tICAgZ2d0aXRsZSgiTUFUVVJFIEIgQ0VMTCIpIC0tPgo8IS0tIGBgYCAtLT4KPCEtLSBgYGB7cn0gLS0+CjwhLS0gc2NlX21hdHVyZUIgPC0gc2NlWyxzY2UkYW5ub19sdmxfMl9maW5hbF9jbGVhbj09Ik5LIl0gLS0+CjwhLS0gYXNzYXkoc2NlX21hdHVyZUIsICJzY2FsZWRfbG9nY291bnRzIikgPC0gdChzY2FsZSh0KGxvZ2NvdW50cyhzY2VfbWF0dXJlQikpKSkgLS0+Cgo8IS0tIHNjZV9tYXR1cmVCIDwtIHJ1blBDQShzY2VfbWF0dXJlQiwgc2NhbGU9RkFMU0UsIG5jb21wb25lbnRzPTMwLCBleHByc192YWx1ZXMgPSAic2NhbGVkX2xvZ2NvdW50cyIpIC0tPgoKPCEtLSAjIyBWYXJpYW5jZSBleHBsYWluZWQgLS0+CjwhLS0gZGF0YS5mcmFtZShyZWR1Y2VkRGltKHNjZV9tYXR1cmVCKVssMTo0XSkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKG9yZ2FuPXNjZV9tYXR1cmVCJG9yZ2FuLCAgLS0+CjwhLS0gICAgICAgICAgY2VsbHR5cGU9c2NlX21hdHVyZUIkYW5ub19sdmxfMl9maW5hbF9jbGVhbikgJT4lIC0tPgo8IS0tICAgbXV0YXRlKGNvbG9yPWlmZWxzZShvcmdhbiAlaW4lIGMoIkdVIiwgIlNQIiksICJFTFAiLCBOQSkpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoUEMxLCBQQzMpKSArIC0tPgo8IS0tICAgZ2VvbV9wb2ludChjb2xvcj0iZ3JleSIpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KGRhdGE9LiAlPiUgZmlsdGVyKCFpcy5uYShjb2xvcikpLCBhZXMoY29sb3I9b3JnYW4pLCBzaXplPTIpICsgLS0+CjwhLS0gICBnZW9tX3J1ZyhkYXRhPS4gJT4lIGZpbHRlcighaXMubmEoY29sb3IpKSwgYWVzKGNvbG9yPW9yZ2FuKSwgYWxwaGE9MC41KSArIC0tPgo8IS0tICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1vcmdfY29sb3JzKSArIC0tPgo8IS0tICAgdGhlbWVfYncoYmFzZV9zaXplPTE2KSArIC0tPgo8IS0tICAgZ2d0aXRsZSgiTksgQ0VMTCIpIC0tPgo8IS0tIGBgYCAtLT4KCgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBkYXRhLmZyYW1lKHJlZHVjZWREaW0oc2NlX21hdHVyZUIpWywyOjNdKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUob3JnYW49c2NlX21hdHVyZUIkb3JnYW4sIGNlbGx0eXBlPXNjZV9tYXR1cmVCJGFubm9fbHZsXzJfZmluYWxfY2xlYW4pICU+JSAtLT4KPCEtLSAgIG11dGF0ZShjb2xvcj1pZmVsc2UoY2VsbHR5cGU9PSJNQVRVUkUgQiBDRUxMIiwgIkVMUCIsIE5BKSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhQQzIsIFBDMykpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KGNvbG9yPSJncmV5IikgKyAtLT4KPCEtLSAgIGdlb21fcG9pbnQoZGF0YT0uICU+JSBmaWx0ZXIoIWlzLm5hKGNvbG9yKSksIGFlcyhjb2xvcj1vcmdhbiksIHNpemU9MikgKyAtLT4KPCEtLSAgIGdlb21fcnVnKGRhdGE9LiAlPiUgZmlsdGVyKCFpcy5uYShjb2xvcikpLCBhZXMoY29sb3I9b3JnYW4pLCBhbHBoYT0wLjUpICsgLS0+CjwhLS0gICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iU3BlY3RyYWwiKSAtLT4KPCEtLSBgYGAgLS0+CgoKIyBGQSBNb2RlbCAtIE5vcm1hbCBNT0ZBIC8gb25seSBjZWxsdHlwZXMgYXMgZ3JvdXBzCgpNYWtlIE1PRkEgb2JqZWN0IChVc2UgY2VsbHR5cGVzIGFzIGdyb3VwaW5nIGNvdmFyaWF0ZSkKCmBgYHtyfQptb2ZhIDwtIGNyZWF0ZV9tb2ZhX2Zyb21fU2luZ2xlQ2VsbEV4cGVyaW1lbnQoc2NlW2FsbF9odmdzLF0sIGFzc2F5ID0gImNvcnJlY3RlZF9sb2djb3VudHMiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwcyA9ICJhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuIiwgZXh0cmFjdF9tZXRhZGF0YSA9IFRSVUUpCgoKCnNhdmVSRFMobW9mYSwgZ2x1ZSgne2luZGlyfUxZTVBIT0lEX21vZmFfb2JqX29yZ2FuQ29ycmVjdGVkLlJEUycpKQptb2ZhX29iaiA8LSByZWFkUkRTKGdsdWUoJ3tpbmRpcn1MWU1QSE9JRF9tb2ZhX29ial9vcmdhbkNvcnJlY3RlZC5SRFMnKSkKYGBgCmBgYHtyfQpvYmplY3QgPC0gbW9mYV9vYmoKYGBgCgoKUHJlcGFyZSA0IHRyYWluaW5nCgpgYGB7cn0KCmRhdGFfb3B0cyA8LSBnZXRfZGVmYXVsdF9kYXRhX29wdGlvbnMob2JqZWN0KQpkYXRhX29wdHMkdXNlX2Zsb2F0MzIgPC0gVFJVRQpkYXRhX29wdHMkY2VudGVyX2dyb3VwcyA8LSBGQUxTRQpvYmplY3RAZGF0YV9vcHRpb25zIDwtIGRhdGFfb3B0cwoKbW9kZWxfb3B0cyA8LSBnZXRfZGVmYXVsdF9tb2RlbF9vcHRpb25zKG9iamVjdCkKbW9kZWxfb3B0cyRudW1fZmFjdG9ycyA8LSAzMAptb2RlbF9vcHRzJGFyZF9mYWN0b3JzIDwtIEZBTFNFCgp0cmFpbl9vcHRzIDwtIGdldF9kZWZhdWx0X3RyYWluaW5nX29wdGlvbnMob2JqZWN0KQp0cmFpbl9vcHRzJHNlZWQgPC0gMjAyMAp0cmFpbl9vcHRzJGNvbnZlcmdlbmNlX21vZGUgPC0gIm1lZGl1bSIgIyB1c2UgImZhc3QiIGZvciBmYXN0ZXIgdHJhaW5pbmcKdHJhaW5fb3B0cyRzdG9jaGFzdGljIDwtIEZBTFNFCgojIG1lZmlzdG9fb3B0cyA8LSBnZXRfZGVmYXVsdF9tZWZpc3RvX29wdGlvbnMob2JqZWN0KQojIG1lZmlzdG9fb3B0cyR3YXJwaW5nIDwtIEZBTFNFCiMgbWVmaXN0b19vcHRzJHNwYXJzZUdQIDwtIFRSVUUKCm9iamVjdCA8LSBwcmVwYXJlX21vZmEoCiAgb2JqZWN0ID0gb2JqZWN0LAogIGRhdGFfb3B0aW9ucyA9IGRhdGFfb3B0cywKICBtb2RlbF9vcHRpb25zID0gbW9kZWxfb3B0cywKICB0cmFpbmluZ19vcHRpb25zID0gdHJhaW5fb3B0cwopIAoKb2JqZWN0CmBgYAoKIyMgVHJhaW4KCldyYXBwZWQgaW4gYHJ1bl9tb2ZhLlJgCgpgYGB7cn0KIyBpbnN0YWxsLnBhY2thZ2VzKHJlbnYpCiMgcmVudjo6aW5pdCgpCnJlbnY6Omluc3RhbGwoInJldGljdWxhdGUiKQpyZW52Ojp1c2VfcHl0aG9uKCkKCnB5X3BrZ3MgPC0gYygKICAgICJzY2FucHkiLAogICAgImFubmRhdGEiLAogICAgIm1vZmFweTIiCikKCnJldGljdWxhdGU6OnB5X2luc3RhbGwocHlfcGtncykKYGBgCgoKYGBge3J9Cm91dGZpbGUgPC0gZ2x1ZSgne2luZGlyfXtzcGxpdH1fbW9mYV9tb2RlbF9vbmV2aWV3X29yZ2FuQ29ycmVjdGVkLmhkZjUnKQptb2ZhX3RyYWluZWQgPC0gcnVuX21vZmEob2JqZWN0LCBvdXRmaWxlID0gb3V0ZmlsZSkKYGBgCgpgYGB7cn0KIyMjIFR3ZWFraW5nIHRoZSBNT0ZBMiBsb2FkaW5nIGZ1bmN0aW9uIGJlY2F1c2UgdGhlIHF1YWxpdHkgY29udHJvbCBjb21wbGFpbnMKbG9hZF9tb2RlbCA8LSBmdW5jdGlvbihmaWxlLCBzb3J0X2ZhY3RvcnMgPSBUUlVFLCBvbl9kaXNrID0gRkFMU0UsIGxvYWRfZGF0YSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgcmVtb3ZlX291dGxpZXJzID0gRkFMU0UsIHJlbW92ZV9pbmFjdGl2ZV9mYWN0b3JzID0gVFJVRSwgdmVyYm9zZSA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgIGxvYWRfaW50ZXJwb2xfWiA9IEZBTFNFKSB7CgogICMgQ3JlYXRlIG5ldyBNT0ZBb2RlbCBvYmplY3QKICBvYmplY3QgPC0gbmV3KCJNT0ZBIikKICBvYmplY3RAc3RhdHVzIDwtICJ0cmFpbmVkIgogIAogICMgU2V0IG9uX2Rpc2sgb3B0aW9uCiAgaWYgKG9uX2Rpc2spIHsgCiAgICBvYmplY3RAb25fZGlzayA8LSBUUlVFIAogIH0gZWxzZSB7IAogICAgICBvYmplY3RAb25fZGlzayA8LSBGQUxTRSAKICB9CiAgCiAgIyBHZXQgZ3JvdXBzIGFuZCBkYXRhIHNldCBuYW1lcyBmcm9tIHRoZSBoZGY1IGZpbGUgb2JqZWN0CiAgaDVscy5vdXQgPC0gaDVscyhmaWxlLCBkYXRhc2V0aW5mbyA9IEZBTFNFKQogIAogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMjIExvYWQgdHJhaW5pbmcgZGF0YSAjIwogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwoKICAjIExvYWQgbmFtZXMKICBpZiAoInZpZXdzIiAlaW4lIGg1bHMub3V0JG5hbWUpIHsKICAgIHZpZXdfbmFtZXMgPC0gYXMuY2hhcmFjdGVyKCBoNXJlYWQoZmlsZSwgInZpZXdzIilbWzFdXSApCiAgICBncm91cF9uYW1lcyA8LSBhcy5jaGFyYWN0ZXIoIGg1cmVhZChmaWxlLCAiZ3JvdXBzIilbWzFdXSApCiAgICBmZWF0dXJlX25hbWVzIDwtIGg1cmVhZChmaWxlLCAiZmVhdHVyZXMiKVt2aWV3X25hbWVzXQogICAgc2FtcGxlX25hbWVzICA8LSBoNXJlYWQoZmlsZSwgInNhbXBsZXMiKVtncm91cF9uYW1lc10gCiAgfSBlbHNlIHsgICMgZm9yIG9sZCBtb2RlbHMKICAgIGZlYXR1cmVfbmFtZXMgPC0gaDVyZWFkKGZpbGUsICJmZWF0dXJlcyIpCiAgICBzYW1wbGVfbmFtZXMgIDwtIGg1cmVhZChmaWxlLCAic2FtcGxlcyIpCiAgICB2aWV3X25hbWVzIDwtIG5hbWVzKGZlYXR1cmVfbmFtZXMpCiAgICBncm91cF9uYW1lcyA8LSBuYW1lcyhzYW1wbGVfbmFtZXMpCiAgICBoNWxzLm91dCA8LSBoNWxzLm91dFtncmVwKCJ2YXJpYW5jZV9leHBsYWluZWQiLCBoNWxzLm91dCRuYW1lLCBpbnZlcnQgPSBUUlVFKSxdCiAgfQogIGlmKCJjb3ZhcmlhdGVzIiAlaW4lICBoNWxzLm91dCRuYW1lKXsKICAgIGNvdmFyaWF0ZV9uYW1lcyA8LSBhcy5jaGFyYWN0ZXIoIGg1cmVhZChmaWxlLCAiY292YXJpYXRlcyIpW1sxXV0pCiAgfSBlbHNlIHsKICAgIGNvdmFyaWF0ZV9uYW1lcyA8LSBOVUxMCiAgfQoKICAjIExvYWQgdHJhaW5pbmcgZGF0YSAoYXMgbmVzdGVkIGxpc3Qgb2YgbWF0cmljZXMpCiAgZGF0YSA8LSBsaXN0KCk7IGludGVyY2VwdHMgPC0gbGlzdCgpCiAgaWYgKGxvYWRfZGF0YSAmJiAiZGF0YSIlaW4laDVscy5vdXQkbmFtZSkgewogICAgCiAgICBvYmplY3RAZGF0YV9vcHRpb25zW1sibG9hZGVkIl1dIDwtIFRSVUUKICAgIGlmICh2ZXJib3NlKSBtZXNzYWdlKCJMb2FkaW5nIGRhdGEuLi4iKQogICAgCiAgICBmb3IgKG0gaW4gdmlld19uYW1lcykgewogICAgICBkYXRhW1ttXV0gPC0gbGlzdCgpCiAgICAgIGludGVyY2VwdHNbW21dXSA8LSBsaXN0KCkKICAgICAgZm9yIChnIGluIGdyb3VwX25hbWVzKSB7CiAgICAgICAgaWYgKG9uX2Rpc2spIHsKICAgICAgICAgICMgYXMgRGVsYXllZEFycmF5cwogICAgICAgICAgZGF0YVtbbV1dW1tnXV0gPC0gRGVsYXllZEFycmF5OjpEZWxheWVkQXJyYXkoIEhERjVBcnJheVNlZWQoZmlsZSwgbmFtZSA9IHNwcmludGYoImRhdGEvJXMvJXMiLCBtLCBnKSApICkKICAgICAgICB9IGVsc2UgewogICAgICAgICAgIyBhcyBtYXRyaWNlcwogICAgICAgICAgZGF0YVtbbV1dW1tnXV0gPC0gaDVyZWFkKGZpbGUsIHNwcmludGYoImRhdGEvJXMvJXMiLCBtLCBnKSApCiAgICAgICAgICB0cnlDYXRjaChpbnRlcmNlcHRzW1ttXV1bW2ddXSA8LSBhcy5udW1lcmljKCBoNXJlYWQoZmlsZSwgc3ByaW50ZigiaW50ZXJjZXB0cy8lcy8lcyIsIG0sIGcpICkgKSwgZXJyb3IgPSBmdW5jdGlvbihlKSB7IE5VTEwgfSkKICAgICAgICB9CiAgICAgICAgIyBSZXBsYWNlIE5hTiBieSBOQQogICAgICAgIGRhdGFbW21dXVtbZ11dW2lzLm5hbihkYXRhW1ttXV1bW2ddXSldIDwtIE5BICMgdGhpcyByZWFsaXNlZCBpbnRvIG1lbW9yeSwgVE8gRklYCiAgICAgIH0KICAgIH0KICAgIAogICMgQ3JlYXRlIGVtcHR5IHRyYWluaW5nIGRhdGEgKGFzIG5lc3RlZCBsaXN0IG9mIGVtcHR5IG1hdHJpY2VzLCB3aXRoIHRoZSBjb3JyZWN0IGRpbWVuc2lvbnMpCiAgfSBlbHNlIHsKICAgIAogICAgb2JqZWN0QGRhdGFfb3B0aW9uc1tbImxvYWRlZCJdXSA8LSBGQUxTRQogICAgCiAgICBmb3IgKG0gaW4gdmlld19uYW1lcykgewogICAgICBkYXRhW1ttXV0gPC0gbGlzdCgpCiAgICAgIGZvciAoZyBpbiBncm91cF9uYW1lcykgewogICAgICAgIGRhdGFbW21dXVtbZ11dIDwtIC5jcmVhdGVfbWF0cml4X3BsYWNlaG9sZGVyKHJvd25hbWVzID0gZmVhdHVyZV9uYW1lc1tbbV1dLCBjb2xuYW1lcyA9IHNhbXBsZV9uYW1lc1tbZ11dKQogICAgICB9CiAgICB9CiAgfQoKICBvYmplY3RAZGF0YSA8LSBkYXRhCiAgb2JqZWN0QGludGVyY2VwdHMgPC0gaW50ZXJjZXB0cwoKCiAgIyBMb2FkIG1ldGFkYXRhIGlmIGFueQogIGlmICgic2FtcGxlc19tZXRhZGF0YSIgJWluJSBoNWxzLm91dCRuYW1lKSB7CiAgICBvYmplY3RAc2FtcGxlc19tZXRhZGF0YSA8LSBiaW5kX3Jvd3MobGFwcGx5KGdyb3VwX25hbWVzLCBmdW5jdGlvbihnKSBhcy5kYXRhLmZyYW1lKGg1cmVhZChmaWxlLCBzcHJpbnRmKCJzYW1wbGVzX21ldGFkYXRhLyVzIiwgZykpKSkpCiAgfQogIGlmICgiZmVhdHVyZXNfbWV0YWRhdGEiICVpbiUgaDVscy5vdXQkbmFtZSkgewogICAgb2JqZWN0QGZlYXR1cmVzX21ldGFkYXRhIDwtIGJpbmRfcm93cyhsYXBwbHkodmlld19uYW1lcywgZnVuY3Rpb24obSkgYXMuZGF0YS5mcmFtZShoNXJlYWQoZmlsZSwgc3ByaW50ZigiZmVhdHVyZXNfbWV0YWRhdGEvJXMiLCBtKSkpKSkKICB9CiAgCiAgIyAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgIyAjIyBMb2FkIHNhbXBsZSBjb3ZhcmlhdGVzICMjCiAgIyAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgIyAKICAjIGlmIChhbnkoZ3JlcGwoImNvdl9zYW1wbGVzIiwgaDVscy5vdXQkZ3JvdXApKSl7CiAgIyAgIGNvdmFyaWF0ZXMgPC0gbGlzdCgpCiAgIyAgIGZvciAoZyBpbiBncm91cF9uYW1lcykgewogICMgICAgIGlmIChvbl9kaXNrKSB7CiAgIyAgICAgICAjIGFzIERlbGF5ZWRBcnJheXMKICAjICAgICAgIGNvdmFyaWF0ZXNbW2ddXSA8LSBEZWxheWVkQXJyYXk6OkRlbGF5ZWRBcnJheSggSERGNUFycmF5U2VlZChmaWxlLCBuYW1lID0gc3ByaW50ZigiY292X3NhbXBsZXMvJXMiLCBnKSApICkKICAjICAgICB9IGVsc2UgewogICMgICAgICAgIyBhcyBtYXRyaWNlcwogICMgICAgICAgY292YXJpYXRlc1tbZ11dIDwtIGg1cmVhZChmaWxlLCBzcHJpbnRmKCJjb3Zfc2FtcGxlcy8lcyIsIGcpICkKICAjICAgICB9ICAgIAogICMgICB9CiAgIyB9IGVsc2UgY292YXJpYXRlcyA8LSBOVUxMCiAgIyBvYmplY3RAY292YXJpYXRlcyA8LSBjb3ZhcmlhdGVzCgogICMgaWYgKGFueShncmVwbCgiY292X3NhbXBsZXNfdHJhbnNmb3JtZWQiLCBoNWxzLm91dCRncm91cCkpKXsKICAjICAgY292YXJpYXRlc193YXJwZWQgPC0gbGlzdCgpCiAgIyAgIGZvciAoZyBpbiBncm91cF9uYW1lcykgewogICMgICAgIGlmIChvbl9kaXNrKSB7CiAgIyAgICAgICAjIGFzIERlbGF5ZWRBcnJheXMKICAjICAgICAgIGNvdmFyaWF0ZXNfd2FycGVkW1tnXV0gPC0gRGVsYXllZEFycmF5OjpEZWxheWVkQXJyYXkoIEhERjVBcnJheVNlZWQoZmlsZSwgbmFtZSA9IHNwcmludGYoImNvdl9zYW1wbGVzX3RyYW5zZm9ybWVkLyVzIiwgZykgKSApCiAgIyAgICAgfSBlbHNlIHsKICAjICAgICAgICMgYXMgbWF0cmljZXMKICAjICAgICAgIGNvdmFyaWF0ZXNfd2FycGVkW1tnXV0gPC0gaDVyZWFkKGZpbGUsIHNwcmludGYoImNvdl9zYW1wbGVzX3RyYW5zZm9ybWVkLyVzIiwgZykgKQogICMgICAgIH0gICAgCiAgIyAgIH0KICAjIH0gZWxzZSBjb3ZhcmlhdGVzX3dhcnBlZCA8LSBOVUxMCiAgIyBvYmplY3RAY292YXJpYXRlc193YXJwZWQgPC0gY292YXJpYXRlc193YXJwZWQKICAKICAjICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgIyAjIyBMb2FkIGludGVycG9sYXRlZCBmYWN0b3IgdmFsdWVzICMjCiAgIyAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMgCiAgIyBpbnRlcnBvbGF0ZWRfWiA8LSBsaXN0KCkKICAjIGlmIChpc1RSVUUobG9hZF9pbnRlcnBvbF9aKSkgewogICMgICAKICAjICAgaWYgKGlzVFJVRSh2ZXJib3NlKSkgbWVzc2FnZSgiTG9hZGluZyBpbnRlcnBvbGF0ZWQgZmFjdG9yIHZhbHVlcy4uLiIpCiAgIyAgIAogICMgICBmb3IgKGcgaW4gZ3JvdXBfbmFtZXMpIHsKICAjICAgICBpbnRlcnBvbGF0ZWRfWltbZ11dIDwtIGxpc3QoKQogICMgICAgIGlmIChvbl9kaXNrKSB7CiAgIyAgICAgICAjIGFzIERlbGF5ZWRBcnJheXMKICAjICAgICAgICMgaW50ZXJwb2xhdGVkX1pbW2ddXSA8LSBEZWxheWVkQXJyYXk6OkRlbGF5ZWRBcnJheSggSERGNUFycmF5U2VlZChmaWxlLCBuYW1lID0gc3ByaW50ZigiWl9wcmVkaWN0aW9ucy8lcyIsIGcpICkgKQogICMgICAgIH0gZWxzZSB7CiAgIyAgICAgICAjIGFzIG1hdHJpY2VzCiAgIyAgICAgICB0cnlDYXRjaCggewogICMgICAgICAgICBpbnRlcnBvbGF0ZWRfWltbZ11dW1sibWVhbiJdXSA8LSBoNXJlYWQoZmlsZSwgc3ByaW50ZigiWl9wcmVkaWN0aW9ucy8lcy9tZWFuIiwgZykgKQogICMgICAgICAgfSwgZXJyb3IgPSBmdW5jdGlvbih4KSB7IHByaW50KCJQcmVkaWNpdGlvbnMgb2YgWiBub3QgZm91bmQsIG5vdCBsb2FkaW5nIGl0Li4uIikgfSkKICAjICAgICAgIHRyeUNhdGNoKCB7CiAgIyAgICAgICAgIGludGVycG9sYXRlZF9aW1tnXV1bWyJ2YXJpYW5jZSJdXSA8LSBoNXJlYWQoZmlsZSwgc3ByaW50ZigiWl9wcmVkaWN0aW9ucy8lcy92YXJpYW5jZSIsIGcpICkKICAjICAgICAgIH0sIGVycm9yID0gZnVuY3Rpb24oeCkgeyBwcmludCgiVmFyaWFuY2Ugb2YgcHJlZGljdGlvbnMgb2YgWiBub3QgZm91bmQsIG5vdCBsb2FkaW5nIGl0Li4uIikgfSkKICAjICAgICAgIHRyeUNhdGNoKCB7CiAgIyAgICAgICAgIGludGVycG9sYXRlZF9aW1tnXV1bWyJuZXdfdmFsdWVzIl1dIDwtIGg1cmVhZChmaWxlLCAiWl9wcmVkaWN0aW9ucy9uZXdfdmFsdWVzIikKICAjICAgICAgIH0sIGVycm9yID0gZnVuY3Rpb24oeCkgeyBwcmludCgiTmV3IHZhbHVlcyBvZiBaIG5vdCBmb3VuZCwgbm90IGxvYWRpbmcgaXQuLi4iKSB9KQogICMgICAgIH0KICAjICAgfQogICMgfQogICMgb2JqZWN0QGludGVycG9sYXRlZF9aIDwtIGludGVycG9sYXRlZF9aCiAgCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKICAjIyBMb2FkIGV4cGVjdGF0aW9ucyAjIwogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCgogIGV4cGVjdGF0aW9ucyA8LSBsaXN0KCkKICBub2RlX25hbWVzIDwtIGg1bHMub3V0W2g1bHMub3V0JGdyb3VwPT0iL2V4cGVjdGF0aW9ucyIsIm5hbWUiXQoKICBpZiAodmVyYm9zZSkgbWVzc2FnZShwYXN0ZTAoIkxvYWRpbmcgZXhwZWN0YXRpb25zIGZvciAiLCBsZW5ndGgobm9kZV9uYW1lcyksICIgbm9kZXMuLi4iKSkKCiAgaWYgKCJBbHBoYVciICVpbiUgbm9kZV9uYW1lcykKICAgIGV4cGVjdGF0aW9uc1tbIkFscGhhVyJdXSA8LSBoNXJlYWQoZmlsZSwgImV4cGVjdGF0aW9ucy9BbHBoYVciKVt2aWV3X25hbWVzXQogIGlmICgiQWxwaGFaIiAlaW4lIG5vZGVfbmFtZXMpCiAgICBleHBlY3RhdGlvbnNbWyJBbHBoYVoiXV0gPC0gaDVyZWFkKGZpbGUsICJleHBlY3RhdGlvbnMvQWxwaGFaIilbZ3JvdXBfbmFtZXNdCiAgaWYgKCJTaWdtYSIgJWluJSBub2RlX25hbWVzKQogICAgZXhwZWN0YXRpb25zW1siU2lnbWEiXV0gPC0gaDVyZWFkKGZpbGUsICJleHBlY3RhdGlvbnMvU2lnbWEiKQogIGlmICgiWiIgJWluJSBub2RlX25hbWVzKQogICAgZXhwZWN0YXRpb25zW1siWiJdXSA8LSBoNXJlYWQoZmlsZSwgImV4cGVjdGF0aW9ucy9aIilbZ3JvdXBfbmFtZXNdCiAgaWYgKCJXIiAlaW4lIG5vZGVfbmFtZXMpCiAgICBleHBlY3RhdGlvbnNbWyJXIl1dIDwtIGg1cmVhZChmaWxlLCAiZXhwZWN0YXRpb25zL1ciKVt2aWV3X25hbWVzXQogIGlmICgiVGhldGFXIiAlaW4lIG5vZGVfbmFtZXMpCiAgICBleHBlY3RhdGlvbnNbWyJUaGV0YVciXV0gPC0gaDVyZWFkKGZpbGUsICJleHBlY3RhdGlvbnMvVGhldGFXIilbdmlld19uYW1lc10KICBpZiAoIlRoZXRhWiIgJWluJSBub2RlX25hbWVzKQogICAgZXhwZWN0YXRpb25zW1siVGhldGFaIl1dIDwtIGg1cmVhZChmaWxlLCAiZXhwZWN0YXRpb25zL1RoZXRhWiIpW2dyb3VwX25hbWVzXQogICMgaWYgKCJUYXUiICVpbiUgbm9kZV9uYW1lcykKICAjICAgZXhwZWN0YXRpb25zW1siVGF1Il1dIDwtIGg1cmVhZChmaWxlLCAiZXhwZWN0YXRpb25zL1RhdSIpCiAgCiAgb2JqZWN0QGV4cGVjdGF0aW9ucyA8LSBleHBlY3RhdGlvbnMKCiAgCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgIyMgTG9hZCBtb2RlbCBvcHRpb25zICMjCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCgogIGlmICh2ZXJib3NlKSBtZXNzYWdlKCJMb2FkaW5nIG1vZGVsIG9wdGlvbnMuLi4iKQoKICB0cnlDYXRjaCggewogICAgb2JqZWN0QG1vZGVsX29wdGlvbnMgPC0gYXMubGlzdChoNXJlYWQoZmlsZSwgJ21vZGVsX29wdGlvbnMnLCByZWFkLmF0dHJpYnV0ZXMgPSBUUlVFKSkKICB9LCBlcnJvciA9IGZ1bmN0aW9uKHgpIHsgcHJpbnQoIk1vZGVsIG9wdGlvbnMgbm90IGZvdW5kLCBub3QgbG9hZGluZyBpdC4uLiIpIH0pCgogICMgQ29udmVydCBUcnVlL0ZhbHNlIHN0cmluZ3MgdG8gbG9naWNhbCB2YWx1ZXMKICBmb3IgKGkgaW4gbmFtZXMob2JqZWN0QG1vZGVsX29wdGlvbnMpKSB7CiAgICBpZiAob2JqZWN0QG1vZGVsX29wdGlvbnNbaV0gPT0gIkZhbHNlIiB8fCBvYmplY3RAbW9kZWxfb3B0aW9uc1tpXSA9PSAiVHJ1ZSIpIHsKICAgICAgb2JqZWN0QG1vZGVsX29wdGlvbnNbaV0gPC0gYXMubG9naWNhbChvYmplY3RAbW9kZWxfb3B0aW9uc1tpXSkKICAgIH0gZWxzZSB7CiAgICAgIG9iamVjdEBtb2RlbF9vcHRpb25zW2ldIDwtIG9iamVjdEBtb2RlbF9vcHRpb25zW2ldCiAgICB9CiAgfQoKICAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKICAjIyBMb2FkIHRyYWluaW5nIG9wdGlvbnMgYW5kIHN0YXRpc3RpY3MgIyMKICAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKCiAgaWYgKHZlcmJvc2UpIG1lc3NhZ2UoIkxvYWRpbmcgdHJhaW5pbmcgb3B0aW9ucyBhbmQgc3RhdGlzdGljcy4uLiIpCgogICMgTG9hZCB0cmFpbmluZyBvcHRpb25zCiAgaWYgKGxlbmd0aChvYmplY3RAdHJhaW5pbmdfb3B0aW9ucykgPT0gMCkgewogICAgdHJ5Q2F0Y2goIHsKICAgICAgb2JqZWN0QHRyYWluaW5nX29wdGlvbnMgPC0gYXMubGlzdChoNXJlYWQoZmlsZSwgJ3RyYWluaW5nX29wdHMnLCByZWFkLmF0dHJpYnV0ZXMgPSBUUlVFKSkKICAgIH0sIGVycm9yID0gZnVuY3Rpb24oeCkgeyBwcmludCgiVHJhaW5pbmcgb3B0cyBub3QgZm91bmQsIG5vdCBsb2FkaW5nIGl0Li4uIikgfSkKICB9CgogICMgTG9hZCB0cmFpbmluZyBzdGF0aXN0aWNzCiAgdHJ5Q2F0Y2goIHsKICAgIG9iamVjdEB0cmFpbmluZ19zdGF0cyA8LSBoNXJlYWQoZmlsZSwgJ3RyYWluaW5nX3N0YXRzJywgcmVhZC5hdHRyaWJ1dGVzID0gVFJVRSkKICAgIG9iamVjdEB0cmFpbmluZ19zdGF0cyA8LSBoNXJlYWQoZmlsZSwgJ3RyYWluaW5nX3N0YXRzJywgcmVhZC5hdHRyaWJ1dGVzID0gVFJVRSkKICB9LCBlcnJvciA9IGZ1bmN0aW9uKHgpIHsgcHJpbnQoIlRyYWluaW5nIHN0YXRzIG5vdCBmb3VuZCwgbm90IGxvYWRpbmcgaXQuLi4iKSB9KQoKICAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMjIExvYWQgY292YXJpYXRlcyBvcHRpb25zICMjCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKICAKICBpZiAoYW55KGdyZXBsKCJjb3Zfc2FtcGxlcyIsIGg1bHMub3V0JGdyb3VwKSkpIHsgCiAgICBpZiAoaXNUUlVFKHZlcmJvc2UpKSBtZXNzYWdlKCJMb2FkaW5nIGNvdmFyaWF0ZXMgb3B0aW9ucy4uLiIpCiAgICB0cnlDYXRjaCggewogICAgICBvYmplY3RAbWVmaXN0b19vcHRpb25zIDwtIGFzLmxpc3QoaDVyZWFkKGZpbGUsICdzbW9vdGhfb3B0cycsIHJlYWQuYXR0cmlidXRlcyA9IFRSVUUpKQogICAgfSwgZXJyb3IgPSBmdW5jdGlvbih4KSB7IHByaW50KCJDb3ZhcmlhdGVzIG9wdGlvbnMgbm90IGZvdW5kLCBub3QgbG9hZGluZyBpdC4uLiIpIH0pCiAgICAKICAgICMgQ29udmVydCBUcnVlL0ZhbHNlIHN0cmluZ3MgdG8gbG9naWNhbCB2YWx1ZXMKICAgIGZvciAoaSBpbiBuYW1lcyhvYmplY3RAbWVmaXN0b19vcHRpb25zKSkgewogICAgICBpZiAob2JqZWN0QG1lZmlzdG9fb3B0aW9uc1tpXSA9PSAiRmFsc2UiIHwgb2JqZWN0QG1lZmlzdG9fb3B0aW9uc1tpXSA9PSAiVHJ1ZSIpIHsKICAgICAgICBvYmplY3RAbWVmaXN0b19vcHRpb25zW2ldIDwtIGFzLmxvZ2ljYWwob2JqZWN0QG1lZmlzdG9fb3B0aW9uc1tpXSkKICAgICAgfSBlbHNlIHsKICAgICAgICBvYmplY3RAbWVmaXN0b19vcHRpb25zW2ldIDwtIG9iamVjdEBtZWZpc3RvX29wdGlvbnNbaV0KICAgICAgfQogICAgfQogICAgCiAgfQogIAogIAogICAgCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgIyMgTG9hZCB2YXJpYW5jZSBleHBsYWluZWQgZXN0aW1hdGVzICMjCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgCiAgaWYgKCJ2YXJpYW5jZV9leHBsYWluZWQiICVpbiUgaDVscy5vdXQkbmFtZSkgewogICAgcjJfbGlzdCA8LSBsaXN0KAogICAgICByMl90b3RhbCA9IGg1cmVhZChmaWxlLCAidmFyaWFuY2VfZXhwbGFpbmVkL3IyX3RvdGFsIilbZ3JvdXBfbmFtZXNdLAogICAgICByMl9wZXJfZmFjdG9yID0gaDVyZWFkKGZpbGUsICJ2YXJpYW5jZV9leHBsYWluZWQvcjJfcGVyX2ZhY3RvciIpW2dyb3VwX25hbWVzXQogICAgKQogICAgb2JqZWN0QGNhY2hlW1sidmFyaWFuY2VfZXhwbGFpbmVkIl1dIDwtIHIyX2xpc3QKICB9CiAgCiAgIyBIYWNrIHRvIGZpeCB0aGUgcHJvYmxlbXMgd2hlcmUgdmFyaWFuY2UgZXhwbGFpbmVkIHZhbHVlcyByYW5nZSBmcm9tIDAgdG8gMSAoJSkKICBpZiAobWF4KHNhcHBseShvYmplY3RAY2FjaGUkdmFyaWFuY2VfZXhwbGFpbmVkJHIyX3RvdGFsLG1heCxuYS5ybT1UUlVFKSxuYS5ybT1UUlVFKTwxKSB7CiAgICBmb3IgKG0gaW4gMTpsZW5ndGgodmlld19uYW1lcykpIHsKICAgICAgZm9yIChnIGluIDE6bGVuZ3RoKGdyb3VwX25hbWVzKSkgewogICAgICAgIG9iamVjdEBjYWNoZSR2YXJpYW5jZV9leHBsYWluZWQkcjJfdG90YWxbW2ddXVtbbV1dIDwtIDEwMCAqIG9iamVjdEBjYWNoZSR2YXJpYW5jZV9leHBsYWluZWQkcjJfdG90YWxbW2ddXVtbbV1dCiAgICAgICAgb2JqZWN0QGNhY2hlJHZhcmlhbmNlX2V4cGxhaW5lZCRyMl9wZXJfZmFjdG9yW1tnXV1bLG1dIDwtIDEwMCAqIG9iamVjdEBjYWNoZSR2YXJpYW5jZV9leHBsYWluZWQkcjJfcGVyX2ZhY3RvcltbZ11dWyxtXQogICAgICB9CiAgICB9CiAgfQogIAogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMjIFNwZWNpZnkgZGltZW5zaW9uYWxpdGllcyAjIwogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogIAogICMgU3BlY2lmeSBkaW1lbnNpb25hbGl0eSBvZiB0aGUgZGF0YQogIG9iamVjdEBkaW1lbnNpb25zW1siTSJdXSA8LSBsZW5ndGgoZGF0YSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBudW1iZXIgb2Ygdmlld3MKICBvYmplY3RAZGltZW5zaW9uc1tbIkciXV0gPC0gbGVuZ3RoKGRhdGFbWzFdXSkgICAgICAgICAgICAgICAgICAgICAgICMgbnVtYmVyIG9mIGdyb3VwcwogIG9iamVjdEBkaW1lbnNpb25zW1siTiJdXSA8LSBzYXBwbHkoZGF0YVtbMV1dLCBuY29sKSAgICAgICAgICAgICAgICAgIyBudW1iZXIgb2Ygc2FtcGxlcyAocGVyIGdyb3VwKQogIG9iamVjdEBkaW1lbnNpb25zW1siRCJdXSA8LSBzYXBwbHkoZGF0YSwgZnVuY3Rpb24oZSkgbnJvdyhlW1sxXV0pKSAgIyBudW1iZXIgb2YgZmVhdHVyZXMgKHBlciB2aWV3KQogIG9iamVjdEBkaW1lbnNpb25zW1siQyJdXSA8LSBucm93KGNvdmFyaWF0ZXNbWzFdXSkgICAgICAgICAgICAgICAgICAgICAgICAjIG51bWJlciBvZiBjb3ZhcmlhdGVzCiAgb2JqZWN0QGRpbWVuc2lvbnNbWyJLIl1dIDwtIG5jb2wob2JqZWN0QGV4cGVjdGF0aW9ucyRaW1sxXV0pICAgICAgICAjIG51bWJlciBvZiBmYWN0b3JzCiAgCiAgIyBBc3NpZ24gc2FtcGxlIGFuZCBmZWF0dXJlIG5hbWVzIChzbG93IGZvciBsYXJnZSBtYXRyaWNlcykKICBpZiAodmVyYm9zZSkgbWVzc2FnZSgiQXNzaWduaW5nIG5hbWVzIHRvIHRoZSBkaWZmZXJlbnQgZGltZW5zaW9ucy4uLiIpCgogICMgQ3JlYXRlIGRlZmF1bHQgZmVhdHVyZXMgbmFtZXMgaWYgdGhleSBhcmUgbnVsbAogIGlmIChpcy5udWxsKGZlYXR1cmVfbmFtZXMpKSB7CiAgICBwcmludCgiRmVhdHVyZXMgbmFtZXMgbm90IGZvdW5kLCBnZW5lcmF0aW5nIGRlZmF1bHQ6IGZlYXR1cmUxX3ZpZXcxLCAuLi4sIGZlYXR1cmVEX3ZpZXdNIikKICAgIGZlYXR1cmVfbmFtZXMgPC0gbGFwcGx5KHNlcV9sZW4ob2JqZWN0QGRpbWVuc2lvbnNbWyJNIl1dKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKG0pIHNwcmludGYoImZlYXR1cmUlZF92aWV3XyZkIiwgYXMuY2hhcmFjdGVyKHNlcV9sZW4ob2JqZWN0QGRpbWVuc2lvbnNbWyJEIl1dW21dKSksIG0pKQogIH0gZWxzZSB7CiAgICAjIENoZWNrIGR1cGxpY2F0ZWQgZmVhdHVyZXMgbmFtZXMKICAgIGFsbF9uYW1lcyA8LSB1bm5hbWUodW5saXN0KGZlYXR1cmVfbmFtZXMpKQogICAgZHVwbGljYXRlZF9uYW1lcyA8LSB1bmlxdWUoYWxsX25hbWVzW2R1cGxpY2F0ZWQoYWxsX25hbWVzKV0pCiAgICBpZiAobGVuZ3RoKGR1cGxpY2F0ZWRfbmFtZXMpPjApIAogICAgICB3YXJuaW5nKCJUaGVyZSBhcmUgZHVwbGljYXRlZCBmZWF0dXJlcyBuYW1lcyBhY3Jvc3MgZGlmZmVyZW50IHZpZXdzLiBXZSB3aWxsIGFkZCB0aGUgc3VmZml4ICpfdmlldyogb25seSBmb3IgdGhvc2UgZmVhdHVyZXMgCiAgICAgICAgICAgIEV4YW1wbGU6IGlmIHlvdSBoYXZlIGJvdGggVFA1MyBpbiBtUk5BIGFuZCBtdXRhdGlvbiBkYXRhIGl0IHdpbGwgYmUgcmVuYW1lZCB0byBUUDUzX21STkEsIFRQNTNfbXV0YXRpb24iKQogICAgZm9yIChtIGluIG5hbWVzKGZlYXR1cmVfbmFtZXMpKSB7CiAgICAgIHRtcCA8LSB3aGljaChmZWF0dXJlX25hbWVzW1ttXV0gJWluJSBkdXBsaWNhdGVkX25hbWVzKQogICAgICBpZiAobGVuZ3RoKHRtcCk+MCkgZmVhdHVyZV9uYW1lc1tbbV1dW3RtcF0gPC0gcGFzdGUoZmVhdHVyZV9uYW1lc1tbbV1dW3RtcF0sIG0sIHNlcD0iXyIpCiAgICB9CiAgfQogIGZlYXR1cmVzX25hbWVzKG9iamVjdCkgPC0gZmVhdHVyZV9uYW1lcwogIAogICMgQ3JlYXRlIGRlZmF1bHQgc2FtcGxlcyBuYW1lcyBpZiB0aGV5IGFyZSBudWxsCiAgaWYgKGlzLm51bGwoc2FtcGxlX25hbWVzKSkgewogICAgcHJpbnQoIlNhbXBsZXMgbmFtZXMgbm90IGZvdW5kLCBnZW5lcmF0aW5nIGRlZmF1bHQ6IHNhbXBsZTEsIC4uLiwgc2FtcGxlTiIpCiAgICBzYW1wbGVfbmFtZXMgPC0gbGFwcGx5KG9iamVjdEBkaW1lbnNpb25zW1siTiJdXSwgZnVuY3Rpb24obikgcGFzdGUwKCJzYW1wbGUiLCBhcy5jaGFyYWN0ZXIoc2VxX2xlbihuKSkpKQogIH0KICBzYW1wbGVzX25hbWVzKG9iamVjdCkgPC0gc2FtcGxlX25hbWVzCgogICMgQWRkIGNvdmFyaWF0ZXMgbmFtZXMKICAjIGlmKCFpcy5udWxsKG9iamVjdEBjb3ZhcmlhdGVzKSl7CiAgIyAgICMgQ3JlYXRlIGRlZmF1bHQgY292YXJpYXRlcyBuYW1lcyBpZiB0aGV5IGFyZSBudWxsCiAgIyAgIGlmIChpcy5udWxsKGNvdmFyaWF0ZV9uYW1lcykpIHsKICAjICAgICBwcmludCgiQ292YXJpYXRlIG5hbWVzIG5vdCBmb3VuZCwgZ2VuZXJhdGluZyBkZWZhdWx0OiBjb3ZhcmlhdGUxLCAuLi4sIGNvdmFyaWF0ZUMiKQogICMgICAgIGNvdmFyaWF0ZV9uYW1lcyA8LSBwYXN0ZTAoInNhbXBsZSIsIGFzLmNoYXJhY3RlcihzZXFfbGVuKG9iamVjdEBkaW1lbnNpb25zW1siQyJdXSkpKQogICMgICB9CiAgIyAgIGNvdmFyaWF0ZXNfbmFtZXMob2JqZWN0KSA8LSBjb3ZhcmlhdGVfbmFtZXMKICAjIH0KICAKICAjIFNldCB2aWV3cyBuYW1lcwogIGlmIChpcy5udWxsKG5hbWVzKG9iamVjdEBkYXRhKSkpIHsKICAgIHByaW50KCJWaWV3cyBuYW1lcyBub3QgZm91bmQsIGdlbmVyYXRpbmcgZGVmYXVsdDogdmlldzEsIC4uLiwgdmlld00iKQogICAgdmlld19uYW1lcyA8LSBwYXN0ZTAoInZpZXciLCBhcy5jaGFyYWN0ZXIoc2VxX2xlbihvYmplY3RAZGltZW5zaW9uc1tbIk0iXV0pKSkKICB9CiAgdmlld3NfbmFtZXMob2JqZWN0KSA8LSB2aWV3X25hbWVzCiAgCiAgIyBTZXQgZ3JvdXBzIG5hbWVzCiAgaWYgKGlzLm51bGwobmFtZXMob2JqZWN0QGRhdGFbWzFdXSkpKSB7CiAgICBwcmludCgiR3JvdXBzIG5hbWVzIG5vdCBmb3VuZCwgZ2VuZXJhdGluZyBkZWZhdWx0OiBncm91cDEsIC4uLiwgZ3JvdXBHIikKICAgIGdyb3VwX25hbWVzIDwtIHBhc3RlMCgiZ3JvdXAiLCBhcy5jaGFyYWN0ZXIoc2VxX2xlbihvYmplY3RAZGltZW5zaW9uc1tbIkciXV0pKSkKICB9CiAgZ3JvdXBzX25hbWVzKG9iamVjdCkgPC0gZ3JvdXBfbmFtZXMKICAKICAjIFNldCBmYWN0b3JzIG5hbWVzCiAgZmFjdG9yc19uYW1lcyhvYmplY3QpICA8LSBwYXN0ZTAoIkZhY3RvciIsIGFzLmNoYXJhY3RlcihzZXFfbGVuKG9iamVjdEBkaW1lbnNpb25zW1siSyJdXSkpKQogIAogICMjIyMjIyMjIyMjIyMjIyMjIyMKICAjIyBQYXJzZSBmYWN0b3JzICMjCiAgIyMjIyMjIyMjIyMjIyMjIyMjIwogIAogICMgQ2FsY3VsYXRlIHZhcmlhbmNlIGV4cGxhaW5lZCBlc3RpbWF0ZXMgcGVyIGZhY3RvcgogIGlmIChpcy5udWxsKG9iamVjdEBjYWNoZVtbInZhcmlhbmNlX2V4cGxhaW5lZCJdXSkpIHsKICAgIG9iamVjdEBjYWNoZVtbInZhcmlhbmNlX2V4cGxhaW5lZCJdXSA8LSBjYWxjdWxhdGVfdmFyaWFuY2VfZXhwbGFpbmVkKG9iamVjdCkKICB9IAogIAogICMgUmVtb3ZlIGluYWN0aXZlIGZhY3RvcnMKICBpZiAocmVtb3ZlX2luYWN0aXZlX2ZhY3RvcnMpIHsKICAgIHIyIDwtIHJvd1N1bXMoZG8uY2FsbCgnY2JpbmQnLCBsYXBwbHkob2JqZWN0QGNhY2hlW1sidmFyaWFuY2VfZXhwbGFpbmVkIl1dJHIyX3Blcl9mYWN0b3IsIHJvd1N1bXMsIG5hLnJtPVRSVUUpKSkKICAgIHZhci50aHJlc2hvbGQgPC0gMC4wMDAxCiAgICBpZiAoYWxsKHIyIDwgdmFyLnRocmVzaG9sZCkpIHsKICAgICAgd2FybmluZyhzcHJpbnRmKCJBbGwgJXMgZmFjdG9ycyB3ZXJlIGZvdW5kIHRvIGV4cGxhaW4gbGl0dGxlIG9yIG5vIHZhcmlhbmNlIHNvIHJlbW92ZV9pbmFjdGl2ZV9mYWN0b3JzIG9wdGlvbiBoYXMgYmVlbiBkaXNhYmxlZC4iLCBsZW5ndGgocjIpKSkKICAgIH0gZWxzZSBpZiAoYW55KHIyIDwgdmFyLnRocmVzaG9sZCkpIHsKICAgICAgb2JqZWN0IDwtIHN1YnNldF9mYWN0b3JzKG9iamVjdCwgd2hpY2gocjI+PXZhci50aHJlc2hvbGQpLCByZWNhbGN1bGF0ZV92YXJpYW5jZV9leHBsYWluZWQ9RkFMU0UpCiAgICAgIG1lc3NhZ2Uoc3ByaW50ZigiJXMgZmFjdG9ycyB3ZXJlIGZvdW5kIHRvIGV4cGxhaW4gbm8gdmFyaWFuY2UgYW5kIHRoZXkgd2VyZSByZW1vdmVkIGZvciBkb3duc3RyZWFtIGFuYWx5c2lzLiBZb3UgY2FuIGRpc2FibGUgdGhpcyBvcHRpb24gYnkgc2V0dGluZyBsb2FkX21vZGVsKC4uLiwgcmVtb3ZlX2luYWN0aXZlX2ZhY3RvcnMgPSBGQUxTRSkiLCBzdW0ocjIgPCB2YXIudGhyZXNob2xkKSkpCiAgICB9CiAgfQogIAogICMgW0RvbmUgaW4gbW9mYXB5Ml0gU29ydCBmYWN0b3JzIGJ5IHRvdGFsIHZhcmlhbmNlIGV4cGxhaW5lZAogIGlmIChzb3J0X2ZhY3RvcnMgJiYgb2JqZWN0QGRpbWVuc2lvbnMkSz4xKSB7CgogICAgIyBTYW5pdHkgY2hlY2tzCiAgICBpZiAodmVyYm9zZSkgbWVzc2FnZSgiUmUtb3JkZXJpbmcgZmFjdG9ycyBieSB0aGVpciB2YXJpYW5jZSBleHBsYWluZWQuLi4iKQoKICAgICMgQ2FsY3VsYXRlIHZhcmlhbmNlIGV4cGxhaW5lZCBwZXIgZmFjdG9yIGFjcm9zcyBhbGwgdmlld3MKICAgIHIyIDwtIHJvd1N1bXMoc2FwcGx5KG9iamVjdEBjYWNoZVtbInZhcmlhbmNlX2V4cGxhaW5lZCJdXSRyMl9wZXJfZmFjdG9yLCBmdW5jdGlvbihlKSByb3dTdW1zKGUsIG5hLnJtID0gVFJVRSkpKQogICAgb3JkZXJfZmFjdG9ycyA8LSBjKG5hbWVzKHIyKVtvcmRlcihyMiwgZGVjcmVhc2luZyA9IFRSVUUpXSkKCiAgICAjIHJlLW9yZGVyIGZhY3RvcnMKICAgIG9iamVjdCA8LSBzdWJzZXRfZmFjdG9ycyhvYmplY3QsIG9yZGVyX2ZhY3RvcnMpCiAgfQoKICAjIE1hc2sgb3V0bGllcnMKICBpZiAocmVtb3ZlX291dGxpZXJzKSB7CiAgICBpZiAodmVyYm9zZSkgbWVzc2FnZSgiUmVtb3Zpbmcgb3V0bGllcnMuLi4iKQogICAgb2JqZWN0IDwtIC5kZXRlY3Rfb3V0bGllcnMob2JqZWN0KQogIH0KICAKICAjIE1hc2sgaW50ZXJjZXB0cyBmb3Igbm9uLUdhdXNzaWFuIGRhdGEKICBpZiAoYW55KG9iamVjdEBtb2RlbF9vcHRpb25zJGxpa2VsaWhvb2RzIT0iZ2F1c3NpYW4iKSkgewogICAgZm9yIChtIGluIG5hbWVzKHdoaWNoKG9iamVjdEBtb2RlbF9vcHRpb25zJGxpa2VsaWhvb2RzIT0iZ2F1c3NpYW4iKSkpIHsKICAgICAgZm9yIChnIGluIG5hbWVzKG9iamVjdEBpbnRlcmNlcHRzW1ttXV0pKSB7CiAgICAgICAgb2JqZWN0QGludGVyY2VwdHNbW21dXVtbZ11dIDwtIE5BCiAgICAgIH0KICAgIH0KICB9CgogICMgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMgIyMgUXVhbGl0eSBjb250cm9scyAjIwogICMgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMgCiAgIyBpZiAodmVyYm9zZSkgbWVzc2FnZSgiRG9pbmcgcXVhbGl0eSBjb250cm9sLi4uIikKICAjIG9iamVjdCA8LSAucXVhbGl0eV9jb250cm9sKG9iamVjdCwgdmVyYm9zZSA9IHZlcmJvc2UpCiAgIyAKICByZXR1cm4ob2JqZWN0KQp9Cgptb2ZhX3RyYWluZWQgPC0gbG9hZF9tb2RlbChvdXRmaWxlKQpzYW1wbGVzX25hbWVzKG1vZmFfdHJhaW5lZCkgPC0gc2FtcGxlc19uYW1lcyhtb2ZhKQpyb3duYW1lcyhzYW1wbGVzX21ldGFkYXRhKG1vZmFfdHJhaW5lZCkpIDwtIHNhbXBsZXNfbWV0YWRhdGEobW9mYV90cmFpbmVkKVtbInNhbXBsZSJdXQpgYGAKCiMjIyBWaXN1YWxpemUgdmFyaWFuY2UgZXhwbGFpbmVkIGJ5IGZhY3RvcnMKCmBgYHtyfQpwbG90X3ZhcmlhbmNlX2V4cGxhaW5lZChtb2ZhX3RyYWluZWQsIHg9J2ZhY3RvcicsIHk9J2dyb3VwJywgc3BsaXRfYnkgPSAndmlldycsIHBsb3RfdG90YWwgPSBUUlVFLCBtYXhfcjIgPSA1MClbWzFdXSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9NDUsIGhqdXN0PTEpKQoKZ2V0X3ZhcmlhbmNlX2V4cGxhaW5lZChtb2ZhX3RyYWluZWQsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKVtbMl1dICU+JQogIGdncGxvdChhZXMoZ3JvdXAsIHZhbHVlKSkgKwogIGdlb21fY29sKCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgeWxhYigiVmFyLiAoJSkiKSArCiAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemU9MTQpCmBgYAoKUGxvdCBieSBjZWxsdHlwZQpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTEwfQpnZXRfdmFyaWFuY2VfZXhwbGFpbmVkKG1vZmFfdHJhaW5lZCwgYXMuZGF0YS5mcmFtZSA9IFRSVUUpW1sxXV0gJT4lCiAgZ2dwbG90KGFlcyhmYWN0b3IsIHZhbHVlKSkgKyBnZW9tX2NvbCgpICsKICBjb29yZF9mbGlwKCkgKwogIGZhY2V0X3dyYXAoZ3JvdXB+LiwgbmNvbCA9IDYsIHNjYWxlcyA9ICJmcmVlX3giKQpgYGAKCmBgYHtyfQpwbG90X2ZhY3Rvcl9jb3IobW9mYV90cmFpbmVkLCBtZXRob2QgPSAic3BlYXJtYW4iKQpgYGAKCgpgYGB7cn0KIyMgQ29ycmVsYXRpb24gd2l0aCBwcmluY2lwYWwgY29tcG9uZW50cwpwY3MgPC0gcmVkdWNlZERpbShzY2UpCmZjdHJzIDwtIGdldF9mYWN0b3JzKG1vZmFfdHJhaW5lZCkgJT4lCiAgcHVycnI6OnJlZHVjZShyYmluZCkKCmNvcnJwbG90Ojpjb3JycGxvdChjb3IocGNzLCBmY3Ryc1tyb3duYW1lcyhwY3MpLF0pKQpgYGAKCiMjIyMgRmFjdG9yIElEIHBsb3RzCgpgYGB7ciwgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTEwfQpwbG90X2ZhY3Rvcl9vcmRlcmVkIDwtIGZ1bmN0aW9uKG1vZmFfdHJhaW5lZCwgZil7CiAgZmFjdG9yX2RmIDwtIGdldF9mYWN0b3JzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGYsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKSAlPiUKICAgICAgbXV0YXRlKG9yZ2FuID0gc2FwcGx5KHN0cl9zcGxpdChzYW1wbGUsICJfIiksIGZ1bmN0aW9uKHgpIHhbMl0pKSAlPiUKICAgICAgZ3JvdXBfYnkoZ3JvdXApICU+JQogICAgICBtdXRhdGUoZ3JfbWVhbiA9IG1lZGlhbih2YWx1ZSkpICU+JQogICAgICB1bmdyb3VwKCkgJT4lCiAgICAgIGFycmFuZ2UoZ3JfbWVhbikgJT4lCiAgICAgIG11dGF0ZShncm91cD1mYWN0b3IoZ3JvdXAsIGxldmVscz11bmlxdWUoZ3JvdXApKSkgCiAgCiAgcjJfZGYgPC0gZ2V0X3ZhcmlhbmNlX2V4cGxhaW5lZChtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSBmLCBhcy5kYXRhLmZyYW1lID0gVFJVRSlbWzFdXSAlPiUKICAgIGZpbHRlcihmYWN0b3I9PXBhc3RlMCgnRmFjdG9yJyxmKSkgJT4lCiAgICBtdXRhdGUoZ3JvdXA9ZmFjdG9yKGdyb3VwLCBsZXZlbHMgPSBsZXZlbHMoZmFjdG9yX2RmJGdyb3VwKSkpCiAgCiAgcGwxIDwtIGZhY3Rvcl9kZiAlPiUKICAgICAgZ2dwbG90KGFlcyhncm91cCwgdmFsdWUpKSArCiAgICAgIGdlb21fYm94cGxvdCgpICsKICAgICAgZ2VvbV9qaXR0ZXIoYWVzKGNvbG9yPSBvcmdhbiksIHNpemU9MC43KSArCiAgICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGxpbmV0eXBlPTIpICsKICAgICAgY29vcmRfZmxpcCgpICsKICAgICAgeWxhYihwYXN0ZTAoIkZhY3RvciAiLCBmKSkgKwogICAgICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNCkKICAKICBwbDIgPC0gcjJfZGYgJT4lCiAgICBnZ3Bsb3QoYWVzKGdyb3VwLCB2YWx1ZSkpICsKICAgIGdlb21fY29sKCkgKwogICAgY29vcmRfZmxpcCgpICsKICAgIHlsYWIoIiUgdmFyaWFuY2UgZXhwbGFpbmVkIikgKwogICAgdGhlbWVfYncoYmFzZV9zaXplID0gMTQpICsKICAgIHJlbW92ZV95X2F4aXMoKQogIAogIHBsMSArIHBsMiArIHBsb3RfbGF5b3V0KHdpZHRocz1jKDIsMSksIGd1aWRlcz0iY29sbGVjdCIpIAp9CgpnZXRfdG9wX2NlbGx0eXBlX3Blcl9mYWN0b3IgPC0gZnVuY3Rpb24obW9mYV90cmFpbmVkLCBmKXsKICByMl9kZiA8LSBnZXRfdmFyaWFuY2VfZXhwbGFpbmVkKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGYsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKVtbMV1dICU+JQogICAgZmlsdGVyKGZhY3Rvcj09cGFzdGUwKCdGYWN0b3InLGYpKSAKICAgICMgbXV0YXRlKGdyb3VwPWZhY3Rvcihncm91cCwgbGV2ZWxzID0gKSkKICB0b3BfcXVhbnRfcjIgPC0gcXVhbnRpbGUocjJfZGYkdmFsdWUsIHByb2JzID0gc2VxKDAsIDEsIGJ5ID0gMC4yKSlbIjgwJSJdCiAgdG9wX2dyb3VwcyA8LSByMl9kZiRncm91cFtyMl9kZiR2YWx1ZSA+PSB0b3BfcXVhbnRfcjJdCiAgcmV0dXJuKHRvcF9ncm91cHMpCn0KCnNhdmVfZmFjdG9yX2lkIDwtIGZ1bmN0aW9uKG1vZmFfdHJhaW5lZCwgZiwgZmlnZGlyKXsKICAjIyBPcmRlciBjZWxsdHlwZXMgYnkgZmFjdG9yIHZhbHVlcwogIHAxIDwtIHBsb3RfZmFjdG9yX29yZGVyZWQobW9mYV90cmFpbmVkLCBmKQogIAogICMjIFBsb3QgZmFjdG9yIHZhbHVlcyBhY3Jvc3Mgb3JnYW5zIGZvciBjZWxsdHlwZXMgd2l0aCBoaWdoIHZhcmlhbmNlIGV4cGxhaW5lZAogIHAyIDwtIHBsb3RfZmFjdG9yKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGYsIGdyb3VwcyA9IGdldF90b3BfY2VsbHR5cGVfcGVyX2ZhY3Rvcihtb2ZhX3RyYWluZWQsIGYpLCBncm91cF9ieSA9ICJncm91cCIsIAogICAgICAgICAgICAgIGNvbG9yX2J5ID0gIm9yZ2FuIiwgCiAgICAgICAgICAgICAgZG90X3NpemUgPSAyLCBkb2RnZSA9IFRSVUUKICAgICAgICAgICAgICApCiAgCiAgIyMgUGxvdCBmYWN0b3Igd2VpZ2h0cyBvbiBnZW5lcwogICMgcGxvdF9kYXRhX2hlYXRtYXAobW9mYV90cmFpbmVkLCBmYWN0b3IgPSBmLCBuZmVhdHVyZXMgPSA1MCwgdGV4dF9zaXplID0gMywgc2hvd19jb2xuYW1lcz1GQUxTRSwKICAjICAgICAgICAgICAgICAgICAgIGFubm90YXRpb25fc2FtcGxlcyA9IGMoIm9yZ2FuIiwgInRpbWUiLCAibWV0aG9kIiwgImRvbm9yIikpCiAgcDMgPC0gcGxvdF93ZWlnaHRzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGYsIG5mZWF0dXJlcyA9IDMwLCB0ZXh0X3NpemUgPSAzKSArCiAgIHNjYWxlX3lfZGlzY3JldGUoZXhwYW5kPWMoMC4xLCAwLjEpKQogIAogIChwMSB8IChwMiAvIHAzKSkgKwogICAgcGxvdF9sYXlvdXQoZ3VpZGVzPSJjb2xsZWN0IikgKwogICAgZ2dzYXZlKGdsdWUoIntmaWdkaXJ9L01PRkFfe3NwbGl0fV9mYWN0b3JJRF9mYWN0b3J7Zn0ucGRmIiksIHdpZHRoID0gMTUsIGhlaWdodCA9IDEwKQp9Cgpmb3IgKGYgaW4gMTptb2ZhX3RyYWluZWRAZGltZW5zaW9ucyRLKXsKICBwcmludChwYXN0ZTAoIlNhdmluZyBJRCBmb3IgRmFjdG9yICIsIGYsICIuLi4iKSkKICBzYXZlX2ZhY3Rvcl9pZChtb2ZhX3RyYWluZWQsIGY9ZiwgZmlnZGlyID0gZmlnZGlyKSAgCn0KCiMgc2F2ZV9mYWN0b3JfaWQobW9mYV90cmFpbmVkLCBmPTEsIGZpZ2RpciA9IGZpZ2RpcikgIAojIHBsb3Rfd2VpZ2h0cyhtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSBmLCBuZmVhdHVyZXMgPSAzMCwgdGV4dF9zaXplID0gMykgKwojICAgIHNjYWxlX3lfZGlzY3JldGUoZXhwYW5kPWMoMC4xLCAwLjEpKQpgYGAKCgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBnZXRfdG9wX2NlbGx0eXBlX3Blcl9mYWN0b3IgPC0gZnVuY3Rpb24obW9mYV90cmFpbmVkLCBmKXsgLS0+CjwhLS0gICByMl9kZiA8LSBnZXRfdmFyaWFuY2VfZXhwbGFpbmVkKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGYsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKVtbMV1dICU+JSAtLT4KPCEtLSAgICAgZmlsdGVyKGZhY3Rvcj09cGFzdGUwKCdGYWN0b3InLGYpKSAlPiUgLS0+CjwhLS0gICAgIG11dGF0ZShncm91cD1mYWN0b3IoZ3JvdXAsIGxldmVscyA9IGxldmVscyhmYWN0b3JfZGYkZ3JvdXApKSkgLS0+CjwhLS0gICB0b3BfcXVhbnRfcjIgPC0gcXVhbnRpbGUocjJfZGYkdmFsdWUsIHByb2JzID0gc2VxKDAsIDEsIGJ5ID0gMC4yKSlbIjgwJSJdIC0tPgo8IS0tICAgdG9wX2dyb3VwcyA8LSByMl9kZiRncm91cFtyMl9kZiR2YWx1ZSA+PSB0b3BfcXVhbnRfcjJdIC0tPgo8IS0tICAgcmV0dXJuKHRvcF9ncm91cHMpIC0tPgo8IS0tIH0gLS0+Cgo8IS0tIHBsb3RfZmFjdG9yKG1vZmFfdHJhaW5lZCwgZmFjdG9yPTIsIGdyb3Vwcz1nZXRfdG9wX2NlbGx0eXBlX3Blcl9mYWN0b3IobW9mYV90cmFpbmVkLCAyKVszOjVdLCBkb2RnZSA9IFRSVUUsIGFkZF9ib3hwbG90ID0gVFJVRSwgY29sb3JfYnk9ImRvbm9yIikgLS0+CjwhLS0gcGxvdF9mYWN0b3IobW9mYV90cmFpbmVkLCBmYWN0b3I9MiwgZ3JvdXBzPWdldF90b3BfY2VsbHR5cGVfcGVyX2ZhY3Rvcihtb2ZhX3RyYWluZWQsIDIpWzM6NV0sIGdyb3VwX2J5ID0gIm9yZ2FuIiwgZG9kZ2UgPSBUUlVFLCBhZGRfYm94cGxvdCA9IFRSVUUsIGNvbG9yX2J5PSJvcmdhbiIpICsgLS0+CjwhLS0gICB5bGltKDMsOCkgLS0+CjwhLS0gYGBgIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBnZXRfZmFjdG9ycyhtb2ZhX3RyYWluZWQsIGdyb3Vwcz1nZXRfdG9wX2NlbGx0eXBlX3Blcl9mYWN0b3IobW9mYV90cmFpbmVkLCAyKVszOjVdLCBmYWN0b3I9MiwgYXMuZGF0YS5mcmFtZSA9IFRSVUUpICU+JSAtLT4KPCEtLSAgIGxlZnRfam9pbihtb2ZhX3RyYWluZWRAc2FtcGxlc19tZXRhZGF0YSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyh2YWx1ZSwgZmlsbD1vcmdhbikpICsgLS0+CjwhLS0gICBnZW9tX2hpc3RvZ3JhbSgpIC0tPgo8IS0tICAgIyBnZW9tX3Ntb290aChtZXRob2Q9ImxtIikgKyAtLT4KPCEtLSAgIGdncHVicjo6c3RhdF9jb3IoKSAtLT4KPCEtLSBwbG90X2ZhY3RvcnNfdnNfY292KG1vZmFfdHJhaW5lZCwgZ3JvdXBzPWdldF90b3BfY2VsbHR5cGVfcGVyX2ZhY3Rvcihtb2ZhX3RyYWluZWQsIDIpWzM6NV0sIGNvdmFyaWF0ZXMgPSAiIikgLS0+CjwhLS0gYGBgIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSB3IDwtIGdldF93ZWlnaHRzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9ICdhbGwnLCBhcy5kYXRhLmZyYW1lID0gRkFMU0UpIC0tPgo8IS0tIGFzLmRhdGEuZnJhbWUodyRzY2FsZWRfbG9nY291bnRzKSAlPiUgLS0+CjwhLS0gICByb3duYW1lc190b19jb2x1bW4oImdlbmUiKSAlPiUgLS0+CjwhLS0gICB3cml0ZV9jc3YoIn4vTU9GQV93ZWlnaHRzLmNzdiIpIC0tPgo8IS0tIGBgYCAtLT4KCiMjIyMgS05OIGdyYXBoIHBlciBjZWxsdHlwZQoKYGBge3J9CiMjIEdldCBmYWN0b3JzIHRoYXQgZXhwbGFpbiBtb3N0IHZhcmlhbmNlIGluIGVhY2ggY2VsbHR5cGUKZ2V0X3RvcF9mYWN0b3JfcGVyX2NlbGx0eXBlIDwtIGZ1bmN0aW9uKG1vZmFfdHJhaW5lZCwgZ3IsIG1pbl9SMj0yKXsKICBnZXRfdmFyaWFuY2VfZXhwbGFpbmVkKG1vZmFfdHJhaW5lZCwgYXMuZGF0YS5mcmFtZSA9IFRSVUUpW1sxXV0gJT4lCiAgICBmaWx0ZXIoZ3JvdXA9PWdyKSAlPiUKICAgIGZpbHRlcih2YWx1ZSA+PSBtaW5fUjIpICU+JQogICAgcHVsbChmYWN0b3IpICU+JQogICAgYXMuY2hhcmFjdGVyKCkKfQoKIyMgTWFrZSBLTk4gZ3JhcGggYmFzZWQgb24gc2ltaWxhcml0eSBvZiB0b3AgZmFjdG9ycyBmb3IgZWFjaCBjZWxsdHlwZQpnZXRfY3RfS05OX2dyYXBoIDwtIGZ1bmN0aW9uKG1vZmFfdHJhaW5lZCwgZ3IsIG1pbl9SMj01LCBrPTUpewogICMjIEdldCBmYWN0b3JzIHRoYXQgZXhwbGFpbiBtb3N0IHZhcmlhbmNlIHBlciBjZWxsdHlwZQogIGZzIDwtIGdldF90b3BfZmFjdG9yX3Blcl9jZWxsdHlwZShtb2ZhX3RyYWluZWQsIGdyLCBtaW5fUjIgPSBtaW5fUjIpCiAgCiAgIyMgTWFrZSBLTk4gZ3JhcGggZnJvbSB0b3AgZmFjdG9ycwogIFogPC0gZ2V0X2ZhY3RvcnMobW9mYV90cmFpbmVkLCBncm91cHM9Z3IsIGZhY3RvcnMgPSBmcylbWzFdXQogIGtubl9jdCA8LSBidWlsZEtOTkdyYXBoKHQoWiksIGs9aykKICAKICAjIyBBZGQgYXR0cmlidXRlcwogIG1ldGFkYXRhX2N0IDwtIHNhbXBsZXNfbWV0YWRhdGEobW9mYV90cmFpbmVkKVtyb3duYW1lcyhaKSxdCiAgIyBjb3ZhcmlhdGVzCiAgVihrbm5fY3QpJG9yZ2FuIDwtIG1ldGFkYXRhX2N0JG9yZ2FuCiAgVihrbm5fY3QpJGFnZSA8LSBtZXRhZGF0YV9jdCRhZ2UKICBWKGtubl9jdCkkbl9jZWxscyA8LSBtZXRhZGF0YV9jdCRuX2NlbGxzCiAgVihrbm5fY3QpJG1ldGhvZCA8LSBtZXRhZGF0YV9jdCRtZXRob2QKICBWKGtubl9jdCkkZG9ub3IgPC0gbWV0YWRhdGFfY3QkZG9ub3IKICAjIHRvcCBmYWN0b3JzCiAgZm9yIChjIGluIGNvbG5hbWVzKFopKXsKICAgdmVydGV4X2F0dHIoa25uX2N0KVtbY11dIDwtIFpbLGNdICAKICB9CiAgCiAgcmV0dXJuKGtubl9jdCkKICB9CgojIyBQbG90IEtOTiBncmFwaApwbG90X2N0X0tOTl9ncmFwaCA8LSBmdW5jdGlvbihrbm4sIGNvbG9yX2J5PSJvcmdhbiIpewogICMjIERlZmluZSBjb2xvciAKICBpZiAoIWNvbG9yX2J5ICVpbiUgbmFtZXModmVydGV4X2F0dHIoa25uKSkpewogICAgc3RvcCgic3BlY2lmaWVkIGNvbG9yX2J5IHZhcmlhYmxlIGlzIG5vdCBpbiB2ZXJ0ZXhfYXR0cihrbm4pIikKICB9CiAgCiAgaWYgKGNvbG9yX2J5PT0ib3JnYW4iKXsgCiAgICBzY2FsZV9jb2xvcl9rbm5ncmFwaCA8LSBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPW9yZ19jb2xvcnMpCiAgfSBlbHNlIGlmIChpcy5udW1lcmljKHZlcnRleF9hdHRyKGtubiwgY29sb3JfYnkpKSl7CiAgICBzY2FsZV9jb2xvcl9rbm5ncmFwaCA8LSBzY2FsZV9jb2xvcl92aXJpZGlzX2Mob3B0aW9uPSJtYWdtYSIpICAKICB9IGVsc2UgewogICAgICBzY2FsZV9jb2xvcl9rbm5ncmFwaCA8LSBzY2FsZV9jb2xvcl9kaXNjcmV0ZSgpCiAgICB9CiAgCiAgdmVydGV4X2F0dHIoa25uLCAiY29sb3JfYnkiKSA8LSB2ZXJ0ZXhfYXR0cihrbm4sIGNvbG9yX2J5KQogIAogIGdncmFwaChrbm4pICsKICAgIGdlb21fZWRnZV9saW5rMCgpICsKICAgIGdlb21fbm9kZV9wb2ludChhZXMoY29sb3I9Y29sb3JfYnksIHNpemU9bl9jZWxscykpICsKICAgIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIHNjYWxlX2NvbG9yX2tubmdyYXBoICsKICAgIHNjYWxlX3NpemUocmFuZ2U9YygyLDcpKSAKICB9CgpnZXRfdG9wX2ZhY3Rvcl9wZXJfY2VsbHR5cGUobW9mYV90cmFpbmVkLCAiQ0Q4K1QiLCBtaW5fUjIgPSA1KQpwbG90X2N0X0tOTl9ncmFwaChnZXRfY3RfS05OX2dyYXBoKG1vZmFfdHJhaW5lZCwgIlNNQUxMIFBSRSBCIENFTEwiLCBrPTUpLCBjb2xvcl9ieSA9ICdvcmdhbicpICsKICBwbG90X2N0X0tOTl9ncmFwaChnZXRfY3RfS05OX2dyYXBoKG1vZmFfdHJhaW5lZCwgIlNNQUxMIFBSRSBCIENFTEwiLCBrPTUpLCBjb2xvcl9ieSA9ICdGYWN0b3I0JykKCmFsbF9ncm91cHMgPC0gbmFtZXMoZ2V0X2RhdGEobW9mYV90cmFpbmVkKVtbMV1dKQprbm5fZ3JhcGhfcGwgPC0gbGFwcGx5KGFsbF9ncm91cHMsIGZ1bmN0aW9uKGcpewogIGtubiA8LSBnZXRfY3RfS05OX2dyYXBoKG1vZmFfdHJhaW5lZCwgZywgaz01LCBtaW5fUjIgPSAyKQogIHBsb3RfY3RfS05OX2dyYXBoKGtubiwgY29sb3JfYnkgPSAnb3JnYW4nKSArIGdndGl0bGUoZykKICB9KQoKa25uX2dyYXBoX3BsIDwtIHNldE5hbWVzKGtubl9ncmFwaF9wbCwgYWxsX2dyb3VwcykKa25uX2dyYXBoX3BsJFRSRUcKYGBgCgpgYGB7cn0KIyMgU2NvcmUgY29ubmVjdGl2aXR5IGJldHdlZW4gc2FtcGxlcyBmcm9tIHRoZSBzYW1lIG9yZ2FuCi5jYWxjX2Nvbm5lY3Rpdml0eV9zY29yZSA8LSBmdW5jdGlvbihrbm4sIG8pewogIGFkaiA8LSBnZXQuYWRqYWNlbmN5KGtubikKICBuX29yZyA8LSBzdW0oVihrbm4pJG9yZ2FuPT1vKQogIG5fb3RoZXIgPC0gc3VtKFYoa25uKSRvcmdhbiE9bykKICB3aXRoaW5fZWRnZXMgPC0gc3VtKGFkaltWKGtubikkb3JnYW49PW8sVihrbm4pJG9yZ2FuPT1vXSkKICBiZXR3ZWVuX2VkZ2VzIDwtIHN1bShhZGpbVihrbm4pJG9yZ2FuPT1vLFYoa25uKSRvcmdhbiE9b10pCiAgc2NvcmUgPC0gKHdpdGhpbl9lZGdlcy9iZXR3ZWVuX2VkZ2VzKSoobl9vdGhlci9uX29yZykKICByZXR1cm4oc2NvcmUpCiAgfQoKIyMgQ2FsY3VsYXRlIGNvbm5lY3Rpdml0eSBzY29yZSBmb3IgcGVybXV0YXRpb25zIG9mIG5vZGUgbGFiZWxzCmNvbm5fc2NvcmVfdGVzdCA8LSBmdW5jdGlvbihrbm4sIG8sIG5fcGVybT0xMDAwKXsKICByZWFsX3Njb3JlIDwtIC5jYWxjX2Nvbm5lY3Rpdml0eV9zY29yZShrbm4sIG8pCiAgIyMgUmFuZG9tIHBlcm11dGF0aW9ucwogIHJhbmRfc2NvcmVzIDwtIGMoKQogIGZvciAoaSBpbiAxOm5fcGVybSl7CiAgICByYW5kX2tubiA8LSBrbm4KICAgIFYocmFuZF9rbm4pJG9yZ2FuIDwtIHNhbXBsZShWKGtubikkb3JnYW4pCiAgICByYW5kX3Njb3JlcyA8LSBjKHJhbmRfc2NvcmVzLCAuY2FsY19jb25uZWN0aXZpdHlfc2NvcmUocmFuZF9rbm4sIG8pKSAgIAogIH0KICAKICBwX3ZhbCA8LSBzdW0oYyhyYW5kX3Njb3JlcywgcmVhbF9zY29yZSkgPj0gcmVhbF9zY29yZSkvKG5fcGVybSArIDEpCiAgaWYgKHBfdmFsIDwgMmUtMTYpeyBwX3ZhbCA8LSAyZS0xNn0KICByZXR1cm4oYygnc2NvcmUnPXJlYWxfc2NvcmUsJ3BfdmFsdWUnPXBfdmFsKSkKfQoKIyMgQ2FsY3VsYXRlIGNvbm5lY3Rpdml0eSBzY29yZSArIHNpZ25pZmljYW5jZSB3aXRoIHBlcm11dGF0aW9uIHRlc3QKdGVzdF9jb25uX2dyb3VwIDwtIGZ1bmN0aW9uKG1vZmFfdHJhaW5lZCwgZywgaz01LCBtaW5fUjIgPSAyLCBuX3Blcm09MTAwMCl7CiAga25uIDwtIGdldF9jdF9LTk5fZ3JhcGgobW9mYV90cmFpbmVkLCBnLCBrPWssIG1pbl9SMiA9IG1pbl9SMikKICB0ZXN0X29yZ3MgPC0gbmFtZXModGFibGUoVihrbm4pJG9yZ2FuKSlbdGFibGUoVihrbm4pJG9yZ2FuKSA+IDJdCiAgcmV0dXJuKHNhcHBseSh0ZXN0X29yZ3MsIGZ1bmN0aW9uKG8pIGNvbm5fc2NvcmVfdGVzdChrbm4sIG8sIG5fcGVybT1uX3Blcm0pKSkKICB9Cgpjb25uZWN0aXZpdHlfdGVzdF9scyA8LSBsYXBwbHkoYWxsX2dyb3VwcywgZnVuY3Rpb24oZykgdGVzdF9jb25uX2dyb3VwKG1vZmFfdHJhaW5lZCwgZykpCmNvbm5lY3Rpdml0eV90ZXN0X2xzIDwtIHNldE5hbWVzKGNvbm5lY3Rpdml0eV90ZXN0X2xzLCBhbGxfZ3JvdXBzKQoKY29ubmVjdGl2aXR5X3Rlc3RfZGYgPC0gaW1hcChjb25uZWN0aXZpdHlfdGVzdF9scywgfiBkYXRhLmZyYW1lKHQoLngpKSAlPiUgcm93bmFtZXNfdG9fY29sdW1uKCJvcmdhbiIpICU+JSBtdXRhdGUoZ3JvdXA9LnkpKSAlPiUKICBwdXJycjo6cmVkdWNlKGJpbmRfcm93cykgJT4lCiAgbXV0YXRlKGlzX3NpZ25pZiA9IGlmZWxzZShwX3ZhbHVlIDwgMC4wMSwgVFJVRSwgRkFMU0UpKSAKCmNvbm5lY3Rpdml0eV90ZXN0X2RmICU+JQogIGdncGxvdChhZXMob3JnYW4sIGdyb3VwLGZpbGw9bG9nMTAoc2NvcmUpKSkgKwogIGdlb21fdGlsZSgpICsKICBzY2FsZV9maWxsX2Rpc3RpbGxlcihwYWxldHRlPSJSZWRzIiwgZGlyZWN0aW9uID0gMSkgKwogIGdlb21fdGV4dChkYXRhPS4gJT4lIGZpbHRlcihpc19zaWduaWYpLCBsYWJlbD0iKiIsIHNpemU9NSkKCmBgYApgYGB7ciwgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTEwfQpjb25uZWN0aXZpdHlfdGVzdF9kZiAlPiUKICBncm91cF9ieShncm91cCkgJT4lCiAgbXV0YXRlKG1lYW5fdmFsPW1lZGlhbihzY29yZSkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBhcnJhbmdlKC1tZWFuX3ZhbCkgJT4lCiAgbXV0YXRlKGdyb3VwPWZhY3Rvcihncm91cCwgbGV2ZWxzPXVuaXF1ZShncm91cCkpKSAlPiUKICBnZ3Bsb3QoYWVzKG9yZ2FuLCBsb2cxcChzY29yZSkpKSArCiAgZ2VvbV9jb2woZmlsbD0iZ3JleSIpICsKICBnZW9tX2NvbChkYXRhPS4gJT4lIGZpbHRlcihpc19zaWduaWYpLCBhZXMoZmlsbD1vcmdhbikpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9b3JnX2NvbG9ycykgICsKICBjb29yZF9mbGlwKCkgKwogIGZhY2V0X2dyaWQoZ3JvdXB+LikgKwogIHRoZW1lKHN0cmlwLnRleHQueSA9IGVsZW1lbnRfdGV4dChhbmdsZT0wKSkKYGBgCgojIyMjIEV4cHJlc3Npb24gb2YgdG9wIFIyIGZhY3RvcnMKCmBgYHtyfQpnZXRfdG9wX3dlaWdodF9nZW5lcyA8LSBmdW5jdGlvbihtb2ZhX3RyYWluZWQsIGYsIG5fdG9wPTIwLCB3aGljaD0idG9wIil7CiAgd19kZiA8LSBnZXRfd2VpZ2h0cyhtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSBmLCBhcy5kYXRhLmZyYW1lID0gVFJVRSkgJT4lCiAgICBhcnJhbmdlKHZhbHVlKSAKICBpZiAod2hpY2g9PSJ0b3AiKSB7CiAgICB3X2RmICU+JQogICAgICB0b3BfbihuX3RvcCwgdmFsdWUpICU+JQogICAgICBwdWxsKGZlYXR1cmUpICU+JQogICAgICBhcy5jaGFyYWN0ZXIoKQogIH0gZWxzZSBpZiAod2hpY2g9PSJib3R0b20iKXsKICAgIHdfZGYgJT4lCiAgICAgIHRvcF9uKG5fdG9wLCAtdmFsdWUpICU+JQogICAgICBwdWxsKGZlYXR1cmUpICU+JQogICAgICBhcy5jaGFyYWN0ZXIoKQogICAgfQp9CgpwbG90X2RhdGFfdG9wX3dlaWdodHMgPC0gZnVuY3Rpb24obW9mYV90cmFpbmVkLCBjdCwgZiwgbl90b3A9MjAsIHdoaWNoPSJ0b3AiKXsKICBnZW5lcyA8LSBnZXRfdG9wX3dlaWdodF9nZW5lcyhtb2ZhX3RyYWluZWQsIGYsIHdoaWNoPXdoaWNoLCBuX3RvcD1uX3RvcCkKICBkYXRhIDwtIGdldF9kYXRhKG1vZmFfdHJhaW5lZCwgZ3JvdXBzPWN0KVtbMV1dW1sxXV1bZ2VuZXMsXQogIAogIHBsX2RmIDwtIHJlc2hhcGUyOjptZWx0KGRhdGEsIHZhcm5hbWVzPWMoImdlbmUiLCAic2FtcGxlIikpICU+JQogICAgbGVmdF9qb2luKHNhbXBsZXNfbWV0YWRhdGEobW9mYV90cmFpbmVkKSkgJT4lCiAgICBhcnJhbmdlKGFnZSkgJT4lCiAgICBtdXRhdGUoc2FtcGxlPWZhY3RvcihzYW1wbGUsIGxldmVscz11bmlxdWUoc2FtcGxlKSkpICU+JQogICAgZ3JvdXBfYnkoZ2VuZSkgJT4lCiAgICBtdXRhdGUodmFsdWU9c2NhbGUodmFsdWUpKQogIHBsX2RmICU+JQogICAgZ2dwbG90KGFlcyhzYW1wbGUsIGdlbmUsIGZpbGw9dmFsdWUpKSArCiAgICBnZW9tX3RpbGUoKSArCiAgICBmYWNldF9ncmlkKC5+b3JnYW4sIHNwYWNlPSJmcmVlIiwgc2NhbGVzPSJmcmVlIikgKwogICAgc2NhbGVfZmlsbF9ncmFkaWVudDIoaGlnaD0icmVkIiwgbG93PSJibHVlIiwgbmFtZT0iU2NhbGVkXG5leHByZXNzaW9uIikgKwogICAgeGxhYigiLS0tLWFnZS0tLT4iKSArIHlsYWIoZ2x1ZSgie3doaWNofSB3ZWlnaHQgZ2VuZXMiKSkgKwogICAgdGhlbWVfYncoYmFzZV9zaXplPTE2KSArCiAgICB0aGVtZShheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgICBnZ3RpdGxlKGdsdWUoJ3tjdH0gLSB7Zn0nKSkKfQoKZm9yIChnIGluIGFsbF9ncm91cHMpewogIGZzIDwtIGdldF90b3BfZmFjdG9yX3Blcl9jZWxsdHlwZShtb2ZhX3RyYWluZWQsIGcsIG1pbl9SMj0zKQogIHRvcF9wbG90cyA8LSBsYXBwbHkoZnMsIGZ1bmN0aW9uKHgpIChwbG90X2RhdGFfdG9wX3dlaWdodHMobW9mYV90cmFpbmVkLCBnLCB4LCB3aGljaD0idG9wIikgKyByZW1vdmVfeF9heGlzKCkpIC8gIAogICAgICAgICAgICAgICAgICAgICAgICBwbG90X2RhdGFfdG9wX3dlaWdodHMobW9mYV90cmFpbmVkLCBnLCB4LCB3aGljaD0iYm90dG9tIikgKyBnZ3RpdGxlKCIiKQogICkKICB3cmFwX3Bsb3RzKHRvcF9wbG90cywgbmNvbD0xKSArCiAgZ2dzYXZlKGdsdWUoIntmaWdkaXJ9L3RvcF9mYWN0b3JzX2V4cHJfe2d9LnBkZiIpLCB3aWR0aD04LCBoZWlnaHQgPSA3Kmxlbmd0aCh0b3BfcGxvdHMpKQp9CgpgYGAKYGBge3IsIGZpZy53aWR0aD0xOH0KcGxvdF9kYXRhX2hlYXRtYXAobW9mYV90cmFpbmVkLCBmYWN0b3IgPSAyLCBzaG93X2NvbG5hbWVzPUZBTFNFLCBhbm5vdGF0aW9uX3NhbXBsZXMgPSBjKCJhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuIiwgIm9yZ2FuIikpCnBsb3RfZmFjdG9yKG1vZmFfdHJhaW5lZCwgZmFjdG9yPTI1LCBjb2xvcl9ieT0ibWV0aG9kIiwgZG90X3NpemUgPSA0KQpgYGAKCiMjIyBHU0VBCmBgYHtyfQojIEJpb2NNYW5hZ2VyOjppbnN0YWxsKCJNT0ZBZGF0YSIpCmxpYnJhcnkoTU9GQWRhdGEpCnV0aWxzOjpkYXRhKHJlYWN0b21lR1MpCmhlYWQocm93bmFtZXMocmVhY3RvbWVHUykpCgojIyBSZW1vdmUgcm93IHdpdGggTkEKcmVhY3RvbWVHUyA8LSByZWFjdG9tZUdTWyFpcy5uYShyb3duYW1lcyhyZWFjdG9tZUdTKSksXQpgYGAKCmBgYHtyfQpsaWJyYXJ5KEVuc0RiLkhzYXBpZW5zLnY4NikKaGcucGFpcnMgPC0gcmVhZFJEUyhzeXN0ZW0uZmlsZSgiZXhkYXRhIiwgImh1bWFuX2N5Y2xlX21hcmtlcnMucmRzIiwgcGFja2FnZT0ic2NyYW4iKSkKYWxsX2dlbmVzIDwtIGVuc2VtYmxkYjo6Z2VuZXMoRW5zRGIuSHNhcGllbnMudjg2KQpkZXRhY2gocGFja2FnZTpFbnNEYi5Ic2FwaWVucy52ODYpCmRldGFjaChwYWNrYWdlOmVuc2VtYmxkYikKCiMgZ2VuZV9uYW1lXzJfaWQgPC0gZnVuY3Rpb24oZ2VuZSl7CiMgICAgcmV0dXJuKGFsbF9nZW5lc1thbGxfZ2VuZXMkZ2VuZV9uYW1lPT1nZW5lLF0kZ2VuZV9pZFsxXSkKIyB9CiMgCiMgZ2VuZV9pZHMgPC0gc2FwcGx5KG1vZmFfdHJhaW5lZEBmZWF0dXJlc19tZXRhZGF0YSRmZWF0dXJlLCBnZW5lX25hbWVfMl9pZCkKIyByb3dEYXRhKHNjZSlbImdlbmVfaWQiXSA8LSBnZW5lX2lkcwojIHJvd0RhdGEoc2NlKVsiZ2VuZV9uYW1lIl0gPC0gcm93bmFtZXMoc2NlKQoKZ2VuZV9uYW1lc19yZWFjdG9tZSA8LSBhbGxfZ2VuZXNbY29sbmFtZXMocmVhY3RvbWVHUyldJGdlbmVfbmFtZQpjb2xuYW1lcyhyZWFjdG9tZUdTKSA8LSBnZW5lX25hbWVzX3JlYWN0b21lCmBgYAoKU3Vic2V0IHRvIGdlbmVzIHRlc3RlZApgYGB7cn0KcmVhY3RvbWVHU191bml2ZXJzZSA8LSByZWFjdG9tZUdTWywgY29sbmFtZXMocmVhY3RvbWVHUykgJWluJSBtb2ZhX3RyYWluZWRAZmVhdHVyZXNfbWV0YWRhdGEkZmVhdHVyZV0KYGBgCgoKYGBge3IsIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD03fQojIEdTRUEgb24gcG9zaXRpdmUgd2VpZ2h0cywgd2l0aCBkZWZhdWx0IG9wdGlvbnMKcmVzLnBvc2l0aXZlIDwtIHJ1bl9lbnJpY2htZW50KG1vZmFfdHJhaW5lZCwKICB2aWV3PSdzY2FsZWRfbG9nY291bnRzJywKICAjIHN0YXRpc3RpY2FsLnRlc3QgPSAnY29yLmFkai5wYXJhbWV0cmljJywKICBmZWF0dXJlLnNldHMgPSByZWFjdG9tZUdTX3VuaXZlcnNlLCAKICBzaWduID0gInBvc2l0aXZlIiwKKQoKIyBHU0VBIG9uIG5lZ2F0aXZlIHdlaWdodHMsIHdpdGggZGVmYXVsdCBvcHRpb25zCnJlcy5uZWdhdGl2ZSA8LSBydW5fZW5yaWNobWVudChtb2ZhX3RyYWluZWQsIAogIHZpZXc9J3NjYWxlZF9sb2djb3VudHMnLAogICMgc3RhdGlzdGljYWwudGVzdCA9ICdjb3IuYWRqLnBhcmFtZXRyaWMnLAogIGZlYXR1cmUuc2V0cyA9IHJlYWN0b21lR1NfdW5pdmVyc2UsIAogIHNpZ24gPSAibmVnYXRpdmUiCikKCgpmb3IgKGYgaW4gMTptb2ZhX3RyYWluZWRAZGltZW5zaW9ucyRLKXsKICBpZiAobWluKHJlcy5wb3NpdGl2ZSRwdmFsLmFkalsscGFzdGUwKCJGYWN0b3IiLCBmKV0pIDwgMC4xKSB7CiAgICBwcmludChwbG90X2VucmljaG1lbnQocmVzLnBvc2l0aXZlLCBmYWN0b3IgPSBmLCBhbHBoYT0wLjEpICsgZ2d0aXRsZSgiUG9zaXRpdmUgd2VpZ2h0cyIpICsKICAgICAgICAgICAgcGxvdF9lbnJpY2htZW50KHJlcy5uZWdhdGl2ZSwgZmFjdG9yID0gZiwgYWxwaGE9MC4xKSArIGdndGl0bGUoIk5lZ2F0aXZlIHdlaWdodHMiKSArCiAgICAgICAgICAgICAgcGxvdF9hbm5vdGF0aW9uKHRpdGxlPXBhc3RlMCgiRmFjdG9yIiwgZikpKQogICAgICB9CiAgfQpgYGAKCmBgYHtyfQpzaWduaWZfcGF0aHdheXMgPC0gcm93bmFtZXMoZGF0YS5mcmFtZShyZXMubmVnYXRpdmUkcHZhbC5hZGopKVtvcmRlcihkYXRhLmZyYW1lKHJlcy5uZWdhdGl2ZSRwdmFsLmFkailbWyJGYWN0b3I4Il1dKVswOjEwXV0KY29sbmFtZXMocmVhY3RvbWVHU191bml2ZXJzZSlbcmVhY3RvbWVHU191bml2ZXJzZVtzaWduaWZfcGF0aHdheXNbNV0sXT09MV0KcGxvdF9lbnJpY2htZW50X2RldGFpbGVkKHJlcy5uZWdhdGl2ZSwgZmFjdG9yID0gOCkKYGBgCgo8IS0tICMjIyBGYWN0b3IgYW5ub3RhdGlvbiAgLS0+CjwhLS0gU28gZmFyIC0tPgoKPCEtLSAjIyMjIEZhY3RvciAxIC0tPgo8IS0tIENlbGwgY3ljbGUgLyBwcm9saWZlcmF0aW9uIHNpZ25hdHVyZSAtLT4KCjwhLS0gIyMjIyBGYWN0b3IgMiAtLT4KPCEtLSBFeHBsYWlucyB2YXJpYXRpb24gaW4gbGF0ZSBCIGNlbGwgc3RhZ2VzLCBwb3NzaWJseSBkaWZmZXJlbmNlIGJldHdlZW4gQk0gYW5kIG90aGVyIG9yZ2Fucz8gLS0+Cgo8IS0tICMjIyMgRmFjdG9yIDMgLS0+CjwhLS0gVGh5bXVzIHNwZWNpZmljIFQgY2VsbCBzaWduYXR1cmUsIGVzcGVjaWFsbHkgaW4gaW1tYXR1cmUgVCBjZWxscy4gSW50ZXJlc3RpbmdseSwgZGlmZmVyZW5jZSBhbHNvIGluIEIxIGNlbGxzLCBjb3VsZCBiZSBzaWduYWxsaW5nIGZyb20gdGh5bWljIG1pY3JvZW52aXJvbm1lbnQ/IC0tPgoKPCEtLSAjIyMjIEZhY3RvciA0IC0tPgo8IS0tIFZhcmlhdGlvbiB3aXRoaW4gcHJvZ2VuaXRvcnMsIGFuZCBsb3RzIG9mIHZhcmlhbmNlIGV4cGxhaW5lZCBpbiBCMSBjZWxscyB0b28hIFN0ZW1uZXNzIG1hcmtlcnMgc3VjaCBhcyBDRDM0LCBIT1BYLi4uIEV4cGxhaW5zIGxvdHMgb2YgdmFyaWFuY2UgaW4gVHJlZ3MgKDcuNjclKSAtLT4KCjwhLS0gIyMjIyBGYWN0b3IgNSAtLT4KPCEtLSBJTEMgc3BlY2lmaWMgLS0+Cgo8IS0tICMjIyMgRmFjdG9yIDcgLS0+CjwhLS0gQ291bGQgYmUgc2lnbmF0dXJlIG9mIHNwbGVlbiBzcGVjaWZpYyBwcm9nZW5pdG9ycywgb3Igc3BsZWVuIHNvdXAgLS0+Cgo8IS0tICMjIyMgRmFjdG9yIDggLS0+CjwhLS0gTW9yZSBjZWxsIGN5Y2xlL3Byb2xpZmVyYXRpb24sIGJ1dCBsb3dlciBpbiB0aHltdXMgc2FtcGxlcywgVEggc2FtcGxlcyBleHByZXNzIHByb3RlYXNvbWUgLS0+Cgo8IS0tICMjIyMgRmFjdG9yIDEwIC0tPgo8IS0tIG1hdHVyZSBWUyBwcm8gQiBjZWxscyAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tIGdldF9mYWN0b3JzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IDcsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKSAlPiUgLS0+CjwhLS0gICBsZWZ0X2pvaW4obW9mYV90cmFpbmVkQHNhbXBsZXNfbWV0YWRhdGEpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXModmFsdWUsIGZpbGw9b3JnYW4pKSArIC0tPgo8IS0tICAgZ2VvbV9oaXN0b2dyYW0oKSAtLT4KPCEtLSBgYGAgLS0+CjwhLS0gYGBge3J9IC0tPgo8IS0tIGxpYnJhcnkocFJPQykgLS0+Cgo8IS0tIGdldF9vcmdhbl9hdWMgPC0gZnVuY3Rpb24obW9mYV90cmFpbmVkLCBmLCBvLCBncm91cHMpeyAtLT4KPCEtLSAgICAgZGYgPC0gZ2V0X2ZhY3RvcnMobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gZiwgYXMuZGF0YS5mcmFtZSA9IFRSVUUsIGdyb3VwcyA9IGdyb3VwcykgJT4lIC0tPgo8IS0tICAgICBsZWZ0X2pvaW4obW9mYV90cmFpbmVkQHNhbXBsZXNfbWV0YWRhdGEpIC0tPgoKPCEtLSAgIGNhdCA8LSBhcy5udW1lcmljKGRmJG9yZ2FuPT1vKSAtLT4KPCEtLSAgIHByZWQgPC0gZGYkdmFsdWUgLS0+CjwhLS0gICBpZiAoc3VtKGNhdCkgPiAwKSB7IC0tPgo8IS0tICAgICByb2Nfb2JqIDwtIHJvYyhjYXQsIHByZWQpIC0tPgo8IS0tICAgICBhdWMgPC0gYXVjKHJvY19vYmopIC0tPgo8IS0tICAgICByZXR1cm4oYXMudmVjdG9yKGF1YykpIC0tPgo8IS0tICAgICB9IC0tPgo8IS0tIH0gLS0+Cgo8IS0tIHRvcF9ncl9kZiA8LSBsYXBwbHkoMToxOSwgZnVuY3Rpb24oZikgZGF0YS5mcmFtZSh0b3BfZ3JvdXA9Z2V0X3RvcF9jZWxsdHlwZV9wZXJfZmFjdG9yKG1vZmFfdHJhaW5lZCwgZiksIGZhY3Rvcj1mKSkgJT4lIC0tPgo8IS0tICAgcHVycnI6OnJlZHVjZShiaW5kX3Jvd3MpICAtLT4KCjwhLS0gb3JnID0gIkJNIiAtLT4KPCEtLSBBVUNfb3JnIDwtIHNhcHBseSgxOm5yb3codG9wX2dyX2RmKSwgZnVuY3Rpb24oaSl7IC0tPgo8IS0tICAgZ2V0X29yZ2FuX2F1Yyhtb2ZhX3RyYWluZWQsICAtLT4KPCEtLSAgICAgICAgICAgICAgICAgbz1vcmcsIC0tPgo8IS0tICAgICAgICAgICAgICAgICBmPXRvcF9ncl9kZiRmYWN0b3JbaV0sICAtLT4KPCEtLSAgICAgICAgICAgICAgICAgZ3JvdXBzID0gdG9wX2dyX2RmJHRvcF9ncm91cFtpXSl9IC0tPgo8IS0tICAgKSAtLT4KPCEtLSBBVUNfb3JnW3NhcHBseShBVUNfb3JnLCBpcy5udWxsKV0gPC0gTkEgLS0+CjwhLS0gdG9wX2dyX2RmW1siQVVDX29yZyJdXSA8LSB1bmxpc3QoQVVDX29yZykgLS0+Cgo8IS0tIGdncGxvdCh0b3BfZ3JfZGYsIGFlcyhmYWN0b3IsIGZpbGw9QVVDX29yZywgdG9wX2dyb3VwKSkgICsgLS0+CjwhLS0gICBnZW9tX3RpbGUoKSArIC0tPgo8IS0tICAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1yb3VuZChBVUNfb3JnLCAyKSkpICsgLS0+CjwhLS0gICBzY2FsZV9maWxsX3ZpcmlkaXNfYygpIC0tPgoKPCEtLSBgYGAgLS0+Cgo8IS0tIC0tLSAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tIGxpYnJhcnkoRW5zRGIuSHNhcGllbnMudjg2KSAtLT4KPCEtLSBoZy5wYWlycyA8LSByZWFkUkRTKHN5c3RlbS5maWxlKCJleGRhdGEiLCAiaHVtYW5fY3ljbGVfbWFya2Vycy5yZHMiLCBwYWNrYWdlPSJzY3JhbiIpKSAtLT4KPCEtLSBhbGxfZ2VuZXMgPC0gZW5zZW1ibGRiOjpnZW5lcyhFbnNEYi5Ic2FwaWVucy52ODYpIC0tPgoKPCEtLSBnZW5lX25hbWVfMl9pZCA8LSBmdW5jdGlvbihnZW5lKXsgLS0+CjwhLS0gICAgcmV0dXJuKGFsbF9nZW5lc1thbGxfZ2VuZXMkZ2VuZV9uYW1lPT1nZW5lLF0kZ2VuZV9pZFsxXSkgLS0+CjwhLS0gfSAtLT4KCjwhLS0gZ2VuZV9pZHMgPC0gc2FwcGx5KHJvd25hbWVzKHNjZSksIGdlbmVfbmFtZV8yX2lkKSAtLT4KPCEtLSByb3dEYXRhKHNjZSlbImdlbmVfaWQiXSA8LSBnZW5lX2lkcyAtLT4KPCEtLSByb3dEYXRhKHNjZSlbImdlbmVfbmFtZSJdIDwtIHJvd25hbWVzKHNjZSkgLS0+Cgo8IS0tIHJvd25hbWVzKHNjZSkgPC0gcm93RGF0YShzY2UpW1siZ2VuZV9pZCJdXSAtLT4KCjwhLS0gYXNzaWdubWVudHMgPC0gY3ljbG9uZShzY2UsIGhnLnBhaXJzLCBhc3NheS50eXBlPSJsb2djb3VudHMiKSAtLT4KCjwhLS0gIyMgQWRkICJwaGFzZSIgYXNzaWdubWVudHMgdG8gbW9mYSAtLT4KPCEtLSBzY2UkY2VsbGN5Y2xlX3BoYXNlIDwtIGFzc2lnbm1lbnRzJHBoYXNlcyAtLT4KPCEtLSBzYW1wbGVzX21ldGFkYXRhKG1vZmFfdHJhaW5lZCkgIDwtIHNhbXBsZXNfbWV0YWRhdGEobW9mYV90cmFpbmVkKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoY2VsbGN5Y2xlX3BoYXNlPXNjZVssbWF0Y2goc2FtcGxlc19tZXRhZGF0YShtb2ZhX3RyYWluZWQpJHNhbXBsZSwgY29sbmFtZXMoc2NlKSldJGNlbGxjeWNsZV9waGFzZSkgLS0+CjwhLS0gYGBgIC0tPgoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gcGxvdF9mYWN0b3JzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IDEsIGNvbG9yX2J5ID0gImNlbGxjeWNsZV9waGFzZSIpIC0tPgo8IS0tIGBgYCAtLT4KCgo8IS0tIDwhLS0gYGBge3IsIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD01fSAtLT4gLS0+CjwhLS0gPCEtLSBnZXRfZmFjdG9ycyhtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSAzLCBhcy5kYXRhLmZyYW1lID0gVFJVRSkgJT4lIC0tPiAtLT4KPCEtLSA8IS0tICAgbXV0YXRlKG9yZ2FuID0gc2FwcGx5KHN0cl9zcGxpdChzYW1wbGUsICItIiksIGZ1bmN0aW9uKHgpIHhbbGVuZ3RoKHgpLTNdKSkgJT4lIC0tPiAtLT4KPCEtLSA8IS0tICAgZ3JvdXBfYnkoZ3JvdXApICU+JSAtLT4gLS0+CjwhLS0gPCEtLSAgIG11dGF0ZShncl9tZWFuID0gbWVkaWFuKHZhbHVlKSkgJT4lIC0tPiAtLT4KPCEtLSA8IS0tICAgdW5ncm91cCgpICU+JSAtLT4gLS0+CjwhLS0gPCEtLSAgIGFycmFuZ2UoZ3JfbWVhbikgJT4lIC0tPiAtLT4KPCEtLSA8IS0tICAgbXV0YXRlKGdyb3VwPWZhY3Rvcihncm91cCwgbGV2ZWxzPXVuaXF1ZShncm91cCkpKSAlPiUgLS0+IC0tPgo8IS0tIDwhLS0gICBnZ3Bsb3QoYWVzKG9yZ2FuLCB2YWx1ZSwgY29sb3I9b3JnYW4pKSArIC0tPiAtLT4KPCEtLSA8IS0tICAgZ2VvbV9ib3hwbG90KCkgKyAtLT4gLS0+CjwhLS0gPCEtLSAgIGdlb21faml0dGVyKCkgKyAtLT4gLS0+CjwhLS0gPCEtLSAgICMgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbGluZXR5cGU9MikgKyAtLT4gLS0+CjwhLS0gPCEtLSAgIGNvb3JkX2ZsaXAoKSArIC0tPiAtLT4KPCEtLSA8IS0tICAgZmFjZXRfd3JhcCgufmdyb3VwLCBzY2FsZXMgPSAiZnJlZV94IikgLS0+IC0tPgo8IS0tIDwhLS0gICAgICAgICAgICAgZ3JvdXBfYnkgPSAiZ3JvdXAiLCAgZG90X3NpemUgPSAwLjgsIGFkZF9ib3hwbG90ID0gVFJVRSwgZG9kZ2UgPSBUUlVFKSArIC0tPiAtLT4KPCEtLSA8IS0tICAgY29vcmRfZmxpcCgpIC0tPiAtLT4KPCEtLSA8IS0tIGBgYCAtLT4gLS0+CgoKPCEtLSAjIyBHbyBieSBjZWxsdHlwZSBpbnN0ZWFkIG9mIGZhY3RvciAtLT4KCjwhLS0gIyMjIERDMSAtLT4KPCEtLSBgYGB7cn0gLS0+CjwhLS0gZ2V0X3ZhcmlhbmNlX2V4cGxhaW5lZChtb2ZhX3RyYWluZWQsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKVtbMV1dICU+JSAtLT4KPCEtLSAgIGZpbHRlcihncm91cD09IkRDMSIpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoZmFjdG9yLCB2YWx1ZSkpICsgZ2VvbV9jb2woKSArIC0tPgo8IS0tICAgY29vcmRfZmxpcCgpICsgLS0+CjwhLS0gICBmYWNldF93cmFwKGdyb3Vwfi4sIG5jb2wgPSA2LCBzY2FsZXMgPSAiZnJlZV94IikgLS0+CjwhLS0gYGBgIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBwbG90X2ZhY3RvcnMobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gYygyLDQpLCBjb2xvcl9ieSA9ICJvcmdhbiIsIGdyb3VwcyA9ICJEQzEiKSAtLT4KPCEtLSBgYGAgLS0+Cgo8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9NH0gLS0+CjwhLS0gcGxvdF9mYWN0b3IobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gYyg0KSwgY29sb3JfYnkgPSAib3JnYW4iLCBncm91cF9ieSA9ICJvcmdhbiIsIGdyb3VwcyA9ICJEQzEiKSAtLT4KPCEtLSBwbG90X2ZhY3Rvcihtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSA0LCBncm91cF9ieSA9ICJncm91cCIsIGNvbG9yX2J5ID0gIm9yZ2FuIiwgZG90X3NpemUgPSAwLjgsIGFkZF9ib3hwbG90ID0gVFJVRSwgZG9kZ2UgPSBUUlVFKSAtLT4KPCEtLSBgYGAgLS0+CjwhLS0gYGBge3J9IC0tPgo8IS0tIHBsb3Rfd2VpZ2h0cyhtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSA0LCBuZmVhdHVyZXMgPSAzMCkgLS0+CjwhLS0gYGBgIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBwbG90X2RhdGFfc2NhdHRlcihtb2ZhX3RyYWluZWQsIGZhY3RvciA9IDQsIGdyb3Vwcz0iREMxIiwgY29sb3I9Im9yZ2FuIiwgZmVhdHVyZXM9IkhMQS1EUkEiKSAtLT4KPCEtLSBgYGAgLS0+Cgo8IS0tICMjIEV4cGxvcmUgYnkgZmFjdG9yIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBwbG90X2ZhY3Rvcihtb2ZhX3RyYWluZWQsIGZhY3RvciA9IDMpIC0tPgo8IS0tIHBsb3Rfd2VpZ2h0cyhtb2ZhX3RyYWluZWQsIGZhY3RvciA9IDMsIG5mZWF0dXJlcyA9IDIwKSAtLT4KPCEtLSBgYGAgLS0+CgoKPCEtLSAjIyBGaW5kIGZhY3RvcnMgdGhhdCBkaXNjcmltaW5hdGUgYmV0d2VlbiBvcmdhbnMgLS0+CgoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gZ2V0X29yZ2FuX0FVQyA8LSBmdW5jdGlvbihtb2ZhX3RyYWluZWQsIGYsIGdyKXsgLS0+CjwhLS0gICBmX2RmIDwtIGdldF9mYWN0b3JzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGYsIGdyb3VwcyA9IGdyLCBhcy5kYXRhLmZyYW1lID0gVFJVRSkgJT4lIC0tPgo8IS0tICAgICAjIGdyb3VwX2J5KGdyb3VwKSAlPiUgLS0+CjwhLS0gICAgICMgbXV0YXRlKHZhbHVlPXNjYWxlKHZhbHVlKSkgJT4lIC0tPgo8IS0tICAgICAjIHVuZ3JvdXAoKSAlPiUgLS0+CjwhLS0gICAgIG11dGF0ZShvcmdhbiA9IHNhcHBseShzdHJfc3BsaXQoc2FtcGxlLCAiLSIpLCBmdW5jdGlvbih4KSB4W2xlbmd0aCh4KS0zXSkpICAtLT4KPCEtLSAgIG9yZ2FucyA8LSB1bmlxdWUoZl9kZiRvcmdhbikgLS0+CjwhLS0gICBzdXBwcmVzc1dhcm5pbmdzKHN1cHByZXNzTWVzc2FnZXMoe29yZ19hdWMgPC0gc2FwcGx5KG9yZ2FucywgZnVuY3Rpb24ob3JnKSByb2MoYXMubnVtZXJpYyhmX2RmJG9yZ2FuPT1vcmcpLCBmX2RmJHZhbHVlKSRhdWMpfSkpIC0tPgo8IS0tICAgYWxsX29yZ2FucyA8LSBhcy5jaGFyYWN0ZXIodW5pcXVlKG1vZmFfdHJhaW5lZEBzYW1wbGVzX21ldGFkYXRhJG9yZ2FuKSkgLS0+CjwhLS0gICBvcmdfYXVjIDwtIHNldE5hbWVzKG9yZ19hdWNbYWxsX29yZ2Fuc10sIGFsbF9vcmdhbnMpIC0tPgo8IS0tICAgcmV0dXJuKG9yZ19hdWMpIC0tPgo8IS0tIH0gLS0+Cgo8IS0tIGFsbF9vcmdhbnMgPC0gYXMuY2hhcmFjdGVyKHVuaXF1ZShtb2ZhX3RyYWluZWRAc2FtcGxlc19tZXRhZGF0YSRvcmdhbikpIC0tPgo8IS0tIGFsbF9ncm91cHMgPC0gYXMuY2hhcmFjdGVyKHVuaXF1ZShtb2ZhX3RyYWluZWRAc2FtcGxlc19tZXRhZGF0YSRncm91cCkpIC0tPgoKPCEtLSAjIyBNYXNrIGlmIHRvbyBsaXR0bGUgc2FtcGxlcyAtLT4KPCEtLSBuX3NhbXBsZXNfbWF0IDwtIHNhbXBsZXNfbWV0YWRhdGEobW9mYV90cmFpbmVkKSAlPiUgLS0+CjwhLS0gICBncm91cF9ieShvcmdhbiwgZ3JvdXApICU+JSAtLT4KPCEtLSAgIHN1bW1hcmlzZShuX3NhbXBsZXM9bigpKSAlPiUgLS0+CjwhLS0gICBwaXZvdF93aWRlcihpZF9jb2xzPWMoZ3JvdXApLCBuYW1lc19mcm9tPSJvcmdhbiIsIHZhbHVlc19mcm9tPSJuX3NhbXBsZXMiLCB2YWx1ZXNfZmlsbD0wKSAlPiUgLS0+CjwhLS0gICBjb2x1bW5fdG9fcm93bmFtZXMoImdyb3VwIikgJT4lIC0tPgo8IS0tICAgYXMubWF0cml4KCkgLS0+Cgo8IS0tIG1hc2tfcGFpcnMgPC0gdChuX3NhbXBsZXNfbWF0IDwgMykgLS0+Cgo8IS0tIEFVQ19tYXQgPC0gc2FwcGx5KGFsbF9ncm91cHMsIGZ1bmN0aW9uKGcpIGdldF9vcmdhbl9BVUMobW9mYV90cmFpbmVkLCBmPTEwLCBncj1nKSkgLS0+CjwhLS0gQVVDX21hdFttYXNrX3BhaXJzW3Jvd25hbWVzKEFVQ19tYXQpLCBjb2xuYW1lcyhBVUNfbWF0KV1dIDwtIE5BIC0tPgoKPCEtLSBBVUNfdGhyZXNoID0gMC44IC0tPgo8IS0tIHJlc2hhcGUyOjptZWx0KEFVQ19tYXQsIHZhcm5hbWVzPWMoIm9yZ2FuIiwgImdyb3VwIiksIHZhbHVlLm5hbWU9IkFVQyIpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMob3JnYW4sIGdyb3VwKSkgKyAtLT4KPCEtLSAgIGdlb21fcG9pbnQoYWVzKHNpemU9QVVDLCBjb2xvcj1BVUMpKSArIC0tPgo8IS0tICAgZ2VvbV9wb2ludChkYXRhPS4gJT4lIGZpbHRlcihBVUMgPiBBVUNfdGhyZXNoKSwgc2hhcGU9OCwgc2l6ZT0yLGNvbG9yPSJ3aGl0ZSIpICsgLS0+CjwhLS0gICBzY2FsZV9zaXplKGxpbWl0cyA9IGMoMC41LDEpKSArIC0tPgo8IS0tICAgc2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG91cnMgPSBSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwoNSwgIlJlZHMiKSkgLS0+CjwhLS0gYGBgIC0tPgoKCjwhLS0gYGBge3IsIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD00fSAtLT4KPCEtLSBsaWJyYXJ5KHBhdGNod29yaykgLS0+CjwhLS0gcGxvdF9mYWN0b3IobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gNSwgZ3JvdXBfYnkgPSAiZ3JvdXAiLCBjb2xvcl9ieSA9ICJvcmdhbiIsIGRvZGdlID0gVFJVRSwgYWRkX2JveHBsb3QgPSBUUlVFKSAgLS0+Cgo8IS0tICAgcGxvdF9sYXlvdXQoZ3VpZGVzPSJjb2xsZWN0IikgLS0+Cgo8IS0tIGBgYCAtLT4KPCEtLSBgYGB7cn0gLS0+CjwhLS0gcGxvdF93ZWlnaHRzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IDUsIG5mZWF0dXJlcyA9IDMwKSAtLT4KPCEtLSBgYGAgLS0+CjwhLS0gYGBge3J9IC0tPgo8IS0tIHBsb3RfZGF0YV9oZWF0bWFwKG1vZmFfdHJhaW5lZCwgZmFjdG9yID0gNSwgc2hvd19jb2xuYW1lcz1GQUxTRSkgLS0+CjwhLS0gYGBgIC0tPgoKCgo8IS0tICMgTW9kZWwgMyAtICBNRUZJU1RPICAtLT4KCjwhLS0gQWRkIHRpbWUgYXMgY292YXJpYXRlIHRvIHJ1biBNRUZJU1RPIC0tPgoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gIyMgVmVjdG9yIGZvciB0aW1lIGFzc2lnbm1lbnQgLS0+CjwhLS0gdGltZXMgPC0gZGlzdGluY3QoZGF0YS5mcmFtZShhZ2U9c2NlJGFnZSwgbmV3X3NhbXBsZSkpICU+JSAtLT4KPCEtLSAgIGNvbHVtbl90b19yb3duYW1lcygnbmV3X3NhbXBsZScpICU+JSAtLT4KPCEtLSAgIC5bc2FtcGxlX25hbWVzX3VuaXF1ZSxdIC0tPgoKPCEtLSBzYW1wbGVzX21ldGFkYXRhKG1vZmEpW1sidGltZSJdXSA8LSB0aW1lcyAtLT4KCjwhLS0gbW9mYSA8LSBzZXRfY292YXJpYXRlcyhtb2ZhLCBjb3ZhcmlhdGVzID0gInRpbWUiKSAtLT4KPCEtLSBtb2ZhIC0tPgo8IS0tIGBgYCAtLT4KPCEtLSBgYGB7ciwgZmlnLmhlaWdodD0xNSwgZmlnLndpZHRoPTEwfSAtLT4KPCEtLSBnZ19pbnB1dCA8LSBwbG90X2RhdGFfb3ZlcnZpZXcobW9mYSwgLS0+CjwhLS0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNob3dfY292YXJpYXRlID0gVFJVRSwgLS0+CjwhLS0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNob3dfZGltZW5zaW9ucyA9IFRSVUUpICAtLT4KPCEtLSBnZ19pbnB1dCAtLT4KPCEtLSBgYGAgLS0+Cgo8IS0tIDwhLS0gS2VlcCBncm91cHMgdGhhdCBzcGFuIG11bHRpcGxlIHZpZXdzIC0tPiAtLT4KPCEtLSA8IS0tIGBgYHtyfSAtLT4gLS0+CjwhLS0gPCEtLSBncl9zYW1wbGVzIDwtIHNwbGl0KHNhbXBsZXNfbWV0YWRhdGEobW9mYSkkc2FtcGxlLCBzYW1wbGVzX21ldGFkYXRhKG1vZmEpJGdyb3VwKSAtLT4gLS0+CjwhLS0gPCEtLSBhbGwoaXMubmEoZGF0YSRCTVssZ3Jfc2FtcGxlcyRCYXNvcGhpbF0pKSAtLT4gLS0+CjwhLS0gPCEtLSBsYXBwbHkodW5pcXVlKHNhbXBsZXNfbWV0YWRhdGEobW9mYSlbWyJncm91cCJdXSksIGZ1bmN0aW9uKHgpIGRhdGEkQk1bXSkgLS0+IC0tPgoKCjwhLS0gPCEtLSBtb2ZhQGRhdGEgLS0+IC0tPgo8IS0tIDwhLS0gc3Vic2UobW9mYSlbLHNhbXBsZXNfbWV0YWRhdGEobW9mYSlbWyJncm91cCJdXSA9PSAiQmFzb3BoaWwiXSAtLT4gLS0+CjwhLS0gPCEtLSBgYGAgLS0+IC0tPgoKPCEtLSBQcmVwYXJlIDQgdHJhaW5pbmcgLS0+Cgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBkYXRhX29wdHMgPC0gZ2V0X2RlZmF1bHRfZGF0YV9vcHRpb25zKG1vZmEpIC0tPgoKPCEtLSBtb2RlbF9vcHRzIDwtIGdldF9kZWZhdWx0X21vZGVsX29wdGlvbnMobW9mYSkgLS0+CjwhLS0gbW9kZWxfb3B0cyRudW1fZmFjdG9ycyA8LSAxMCAtLT4KCjwhLS0gdHJhaW5fb3B0cyA8LSBnZXRfZGVmYXVsdF90cmFpbmluZ19vcHRpb25zKG1vZmEpIC0tPgo8IS0tIHRyYWluX29wdHMkc2VlZCA8LSAyMDIwIC0tPgo8IS0tIHRyYWluX29wdHMkY29udmVyZ2VuY2VfbW9kZSA8LSAiZmFzdCIgIyB1c2UgImZhc3QiIGZvciBmYXN0ZXIgdHJhaW5pbmcgLS0+Cgo8IS0tIG1lZmlzdG9fb3B0cyA8LSBnZXRfZGVmYXVsdF9tZWZpc3RvX29wdGlvbnMobW9mYSkgLS0+CjwhLS0gbWVmaXN0b19vcHRzJHdhcnBpbmcgPC0gRkFMU0UgLS0+CjwhLS0gIyBtZWZpc3RvX29wdHMkc3BhcnNlR1AgPC0gVFJVRSAtLT4KCjwhLS0gbW9mYSA8LSBwcmVwYXJlX21vZmEoIC0tPgo8IS0tICAgb2JqZWN0ID0gbW9mYSwgLS0+CjwhLS0gICBkYXRhX29wdGlvbnMgPSBkYXRhX29wdHMsIC0tPgo8IS0tICAgbW9kZWxfb3B0aW9ucyA9IG1vZGVsX29wdHMsIC0tPgo8IS0tICAgdHJhaW5pbmdfb3B0aW9ucyA9IHRyYWluX29wdHMsIC0tPgo8IS0tICAgbWVmaXN0b19vcHRpb25zID0gbWVmaXN0b19vcHRzIC0tPgo8IS0tICkgIC0tPgo8IS0tIGBgYCAtLT4KCjwhLS0gIyMgVHJhaW4gLS0+Cgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBvdXRmaWxlIDwtICIvbmZzL3RlYW0yMDUvZWQ2L2RhdGEvRmV0YWxfaW1tdW5lL215ZWxvaWRfbWVmaXN0b19tb2RlbC5oZGY1IiAtLT4KPCEtLSBtb2ZhX3RyYWluZWQgPC0gcnVuX21vZmEobW9mYSwgb3V0ZmlsZSA9IG91dGZpbGUpIC0tPgo8IS0tIGBgYCAtLT4KCjwhLS0gIyMgTG9hZCB0cmFpbmVkIG1vZGVsIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBtb2ZhX3RyYWluZWQgPC0gbG9hZF9tb2RlbChvdXRmaWxlLCBsb2FkX2ludGVycG9sX1ogPSBUUlVFKSAtLT4KPCEtLSBgYGAgLS0+Cgo=